com.manydesigns.portofino.pageactions.crud.CrudAction4ItsProject.java Source code

Java tutorial

Introduction

Here is the source code for com.manydesigns.portofino.pageactions.crud.CrudAction4ItsProject.java

Source

/*
 * Copyright (C) 2005-2013 ManyDesigns srl.  All rights reserved.
 * http://www.manydesigns.com/
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package com.manydesigns.portofino.pageactions.crud;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.write.DateFormat;
import jxl.write.DateTime;
import jxl.write.Number;
import jxl.write.NumberFormat;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.StreamingResolution;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.shiro.codec.Base64;
import org.hibernate.exception.ConstraintViolationException;
import org.xml.sax.SAXException;

import com.manydesigns.elements.ElementsThreadLocals;
import com.manydesigns.elements.Mode;
import com.manydesigns.elements.fields.DateField;
import com.manydesigns.elements.fields.Field;
import com.manydesigns.elements.fields.NumericField;
import com.manydesigns.elements.fields.PasswordField;
import com.manydesigns.elements.forms.FieldSet;
import com.manydesigns.elements.forms.FormBuilder;
import com.manydesigns.elements.forms.TableForm;
import com.manydesigns.elements.forms.TableFormBuilder4ExportAll;
import com.manydesigns.elements.messages.SessionMessages;
import com.manydesigns.elements.util.MimeTypes;
import com.manydesigns.elements.xml.XmlBuffer;
import com.manydesigns.portofino.buttons.annotations.Button;
import com.manydesigns.portofino.files.TempFile;
import com.manydesigns.portofino.files.TempFileService;
import com.manydesigns.portofino.pageactions.PageActionName;
import com.manydesigns.portofino.pageactions.annotations.ConfigurationClass;
import com.manydesigns.portofino.pageactions.annotations.ScriptTemplate;
import com.manydesigns.portofino.pageactions.annotations.SupportsDetail;
import com.manydesigns.portofino.pageactions.crud.configuration.CrudConfiguration;
import com.manydesigns.portofino.persistence.QueryUtils;
import com.manydesigns.portofino.security.AccessLevel;
import com.manydesigns.portofino.security.RequiresPermissions;
import com.manydesigns.portofino.security.SupportsPermissions;
import com.manydesigns.portofino.utils.ContextUtils;

/**
 * Default AbstractCrudAction implementation. Implements a crud page over a database table, based on a HQL query.
 * 
 * @author Paolo Predonzani - paolo.predonzani@manydesigns.com
 * @author Angelo Lupo - angelo.lupo@manydesigns.com
 * @author Giampiero Granatella - giampiero.granatella@manydesigns.com
 * @author Alessio Stalla - alessio.stalla@manydesigns.com
 */
@SupportsPermissions({ CrudAction4ItsProject.PERMISSION_CREATE, CrudAction4ItsProject.PERMISSION_EDIT,
        CrudAction4ItsProject.PERMISSION_DELETE })
@RequiresPermissions(level = AccessLevel.VIEW)
@ScriptTemplate("script_template.groovy")
@ConfigurationClass(CrudConfiguration.class)
@SupportsDetail
@PageActionName("Crud")
public class CrudAction4ItsProject extends CrudAction4UpdateByRole {

    @Override
    protected void commitTransaction() {
        session.getTransaction().commit();
    }

    /**
     * Configures the builder for the search detail (view, create, edit) form. You can override this method to customize
     * how the form is generated (e.g. adding custom links on specific properties, hiding or showing properties based on
     * some runtime condition, etc.).
     * 
     * @param formBuilder the form builder.
     * @param mode the mode of the form.
     * @return the form builder.
     */
    protected FormBuilder configureFormBuilder(FormBuilder formBuilder, Mode mode) {
        formBuilder.configPrefix(prefix).configMode(mode);
        configureFormSelectionProviders(formBuilder);
        // hongliangpan add
        if (classAccessor.getProperties().length > 16) {
            formBuilder.configNColumns(3);
        } else if (classAccessor.getProperties().length > 8) {
            formBuilder.configNColumns(2);
        }
        return formBuilder;
    }

    @Override
    protected void doSave(Object object) {
        try {
            processPassword(object);
            processSortId(object);
            processToken(object);
            processExpired4Warranty(object);
            processModifyUser(object);
            session.save(baseTable.getActualEntityName(), object);

        } catch (ConstraintViolationException e) {
            processException(e);
        } catch (Exception e) {
            logger.warn("Constraint violation in update", e);
            throw new RuntimeException(
                    ElementsThreadLocals.getText("save.failed.because.constraint.violated") + e.getMessage());
        }
    }

    public void processException(ConstraintViolationException e) {
        String t_message = e.getCause().getMessage();
        if (t_message.indexOf("Duplicate entry") >= 0 && t_message.indexOf("_name") >= 0) {
            t_message = "????" + getFieldValue(t_message) + "";
        } else if (t_message.indexOf("Duplicate entry") >= 0) {
            t_message = "???" + getFieldValue(t_message) + "";
        }
        logger.warn("Constraint violation in update" + t_message, e);
        throw new RuntimeException(
                ElementsThreadLocals.getText("save.failed.because.constraint.violated") + t_message);
    }

    public String getFieldValue(String message) {
        return message.substring(message.indexOf("'") + 1, message.indexOf("' for key"));
    }

    @Override
    protected void doUpdate(Object object) {
        try {
            processPassword(object);
            processShortName(object);
            processSortId(object);
            processToken(object);
            processExpired4Warranty(object);
            processModifyUser(object);
            // processExpired(object);
            session.update(baseTable.getActualEntityName(), object);
        } catch (ConstraintViolationException e) {
            processException(e);
        }
    }

    public void processPassword(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        String t_password = "c_password";
        if (t_map.containsKey(t_password)) {
            Object t_value = t_map.get(t_password);
            t_value = encryptPassword(t_value.toString());
            t_map.put(t_password, t_value);
        }
    }

    String encryptPassword(String password) {
        return md5Base64(password);
    }

    String md5Base64(String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(password.getBytes("UTF-8"));
            byte[] raw = md.digest();
            return toBase64(raw);
        } catch (NoSuchAlgorithmException e) {
            processException(e);
        } catch (UnsupportedEncodingException e) {
            processException(e);
        }
        return password;
    }

    private void processException(Exception e) {
        logger.error(e.getMessage(), e);
    }

    protected String toBase64(byte[] bytes) {
        return Base64.encodeToString(bytes);
    }

    // hongliangpan add
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void processShortName(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        String t_shortName = "c_short_name";
        String c_name = "c_name";
        if (t_map.containsKey(t_shortName) && t_map.containsKey(c_name)) {
            Object t_value = t_map.get(t_shortName);
            if (StringUtils.isBlank(String.valueOf(t_value))) {
                t_value = t_map.get(c_name);
                t_map.put(t_shortName, t_value);
            }
        }
    }

    // hongliangpan add
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void processSortId(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        String t_sortId = "c_sort_id";
        String t_id = "c_id";
        Object t_value = t_map.get(t_sortId);
        if (t_map.containsKey(t_sortId) && t_map.containsKey(t_id)) {
            if (null == t_value || "0".equals(String.valueOf(t_value))) {
                t_value = t_map.get(t_id);
                if (org.apache.commons.lang3.math.NumberUtils.isNumber(t_value.toString())) {
                    t_map.put(t_sortId, t_value);
                }
            }
        } else if (t_map.containsKey(t_sortId)
                && (null == t_value || StringUtils.isBlank((String.valueOf(t_value))))) {
            String table = this.getBaseTable().getTableName();
            String sql = "SELECT max(c_id)+1 FROM " + table;
            t_value = QueryUtils.runSql(session, sql).get(0)[0];
            t_map.put(t_sortId, t_value);
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void processToken(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        String t_sortId = "c_token";
        Object t_value = t_map.get(t_sortId);
        if (t_map.containsKey(t_sortId)) {
            if (null == t_value || "".equals(String.valueOf(t_value))) {
                t_map.put(t_sortId, UUID.randomUUID().toString());
            }
        }
    }

    // hongliangpan add
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void processSortIdAfter(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        String t_sortId = "c_sort_id";
        String t_id = "c_id";
        if (t_map.containsKey(t_sortId) && t_map.containsKey(t_id)) {
            String table = this.getBaseTable().getTableName();
            String updateSql = "UPDATE " + table + " SET c_sort_id =c_id WHERE c_sort_id <= 0 OR c_sort_id IS NULL";
            QueryUtils.runSql(session, updateSql);
            // TODO
        }
    }

    public void processModifyUser(Object object) {
        if (!(object instanceof HashMap)) {
            return;
        }

        HashMap t_map = (HashMap) object;
        // if (!t_map.containsKey("c_modify_time")) {
        // return;
        // }
        String t_field = "c_create_time";
        Object t_value = t_map.get(t_field);
        if (null == t_value) {
            t_map.put(t_field, new Date());
        }

        String t_userName = ContextUtils.getLoginUser();
        t_field = "c_create_user";
        t_value = t_map.get(t_field);
        if (null == t_value) {
            t_map.put(t_field, t_userName);
        }

        t_field = "c_modify_time";
        t_map.put(t_field, new Date());
        t_field = "c_modify_user";
        t_map.put(t_field, t_userName);
    }

    // **************************************************************************
    // Object loading
    // **************************************************************************

    public void loadObjects() {
        super.loadObjects();
        processExpired();
    }

    // hongliangpan add
    public void processExpired() {
        for (Object row : objects) {
            processExpired(row);
        }
    }

    // hongliangpan add
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void processExpiredOldDelete(Object row) {
        Map rowMap = (HashMap) row;
        Date t_time = (Date) rowMap.get("c_warranty_end");
        if (null != t_time) {
            rowMap.put("c_is_expired", t_time.before(DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH)));
        }
    }

    // hongliangpan add
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void processExpired(Object row) {
        Map rowMap = (HashMap) row;
        Date t_time = (Date) rowMap.get("c_warranty_end");
        if (null != t_time) {
            rowMap.put("c_is_expired", t_time.before(DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH)));
        }
    }

    // hongliangpan add
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void processExpired4Warranty(Object row) {
        Map rowMap = (HashMap) row;
        Date t_timeEnd = (Date) rowMap.get("c_warranty_end");
        Date t_timeBegin = (Date) rowMap.get("c_warranty_begin");
        if (null != t_timeEnd && null != t_timeBegin) {
            long timeBetween = (DateUtils.truncate(t_timeEnd, Calendar.DAY_OF_MONTH).getTime()

                    - DateUtils.truncate(t_timeBegin, Calendar.DAY_OF_MONTH).getTime()) / 86400000;
            // / 1000 / 60 / 60 / 24
            timeBetween = (long) Math.ceil(((timeBetween + 1) / 30f));
            rowMap.put("c_warranty", String.valueOf(timeBetween));
            rowMap.put("c_is_expired", t_timeEnd.before(DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH)));
        }
    }

    // hongliangpan copy from 4.0.10
    // **************************************************************************
    // ExportSearch
    // **************************************************************************
    // --------------------------------------------------------------------------
    // Export
    // --------------------------------------------------------------------------

    protected static final String TEMPLATE_FOP_SEARCH = "templateFOP-Search.xsl";
    protected static final String TEMPLATE_FOP_READ = "templateFOP-Read.xsl";

    @Button(list = "crud-search", key = "commons.exportExcel", order = 9, group = "crud", icon = " icon-th ")
    public Resolution exportSearchExcel() {
        try {
            TempFileService fileService = TempFileService.getInstance();
            TempFile tempFile = fileService.newTempFile("application/vnd.ms-excel",
                    crudConfiguration.getSearchTitle() + ".xls");
            OutputStream outputStream = tempFile.getOutputStream();
            exportSearchExcel(outputStream);
            outputStream.flush();
            outputStream.close();
            return fileService.stream(tempFile);
        } catch (Exception e) {
            logger.error("Excel export failed", e);
            SessionMessages.addErrorMessage(getMessage("commons.export.failed"));
            // return new RedirectResolution(getDispatch().getOriginalPath());
            return new RedirectResolution(getReturnUrl());
        }
    }

    public void exportSearchExcel(OutputStream outputStream) {
        setupSearchForm();
        loadObjects();
        setupTableForm4ExportAll(Mode.VIEW);

        writeFileSearchExcel(outputStream);
    }

    protected void setupTableForm4ExportAll(Mode mode) {
        TableFormBuilder4ExportAll tableFormBuilder = new TableFormBuilder4ExportAll(classAccessor);
        configureTableFormSelectionProviders(tableFormBuilder);

        int nRows;
        if (objects == null) {
            nRows = 0;
        } else {
            nRows = objects.size();
        }

        configureTableFormBuilder(tableFormBuilder, mode, nRows);
        tableForm = buildTableForm(tableFormBuilder);

        if (objects != null) {
            tableForm.readFromObject(objects);
            refreshTableBlobDownloadHref();
        }
    }

    private void writeFileSearchExcel(OutputStream outputStream) {
        WritableWorkbook workbook = null;
        try {
            WorkbookSettings workbookSettings = new WorkbookSettings();
            workbookSettings.setUseTemporaryFileDuringWrite(false);
            workbook = Workbook.createWorkbook(outputStream, workbookSettings);
            String title = crudConfiguration.getSearchTitle();
            if (StringUtils.isBlank(title)) {
                title = "export";
            }
            WritableSheet sheet = workbook.createSheet(title, 0);

            addHeaderToSearchSheet(sheet);

            int i = 1;
            for (TableForm.Row row : tableForm.getRows()) {
                exportRows(sheet, i, row);
                i++;
            }

            workbook.write();
        } catch (IOException e) {
            logger.warn("IOException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } catch (RowsExceededException e) {
            logger.warn("RowsExceededException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } catch (WriteException e) {
            logger.warn("WriteException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } finally {
            try {
                if (workbook != null)
                    workbook.close();
            } catch (Exception e) {
                logger.warn("IOException", e);
                SessionMessages.addErrorMessage(e.getMessage());
            }
        }
    }

    // **************************************************************************
    // ExportRead
    // **************************************************************************

    @Button(list = "crud-read", key = "commons.exportExcel", order = 9)
    public Resolution exportReadExcel() {
        try {
            TempFileService fileService = TempFileService.getInstance();
            TempFile tempFile = fileService.newTempFile("application/vnd.ms-excel",
                    crudConfiguration.getReadTitle() + ".xls");
            OutputStream outputStream = tempFile.getOutputStream();
            exportReadExcel(outputStream);
            outputStream.flush();
            outputStream.close();
            return fileService.stream(tempFile);
        } catch (Exception e) {
            logger.error("Excel export failed", e);
            SessionMessages.addErrorMessage(getMessage("commons.export.failed"));
            // return new RedirectResolution(getDispatch().getOriginalPath());
            return new RedirectResolution(getReturnUrl());
        }
    }

    public void exportReadExcel(OutputStream outputStream) throws IOException, WriteException {
        setupSearchForm();

        loadObjects();

        setupForm(Mode.VIEW);
        form.readFromObject(object);

        writeFileReadExcel(outputStream);
    }

    private void writeFileReadExcel(OutputStream outputStream) throws IOException, WriteException {
        WritableWorkbook workbook = null;
        try {
            WorkbookSettings workbookSettings = new WorkbookSettings();
            workbookSettings.setUseTemporaryFileDuringWrite(false);
            workbook = Workbook.createWorkbook(outputStream, workbookSettings);
            WritableSheet sheet = workbook.createSheet(crudConfiguration.getReadTitle(),
                    workbook.getNumberOfSheets());

            addHeaderToReadSheet(sheet);

            int i = 1;
            for (FieldSet fieldset : form) {
                int j = 0;
                for (Field field : fieldset.fields()) {
                    addFieldToCell(sheet, i, j, field);
                    j++;
                }
                i++;
            }
            workbook.write();
        } catch (IOException e) {
            logger.warn("IOException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } catch (RowsExceededException e) {
            logger.warn("RowsExceededException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } catch (WriteException e) {
            logger.warn("WriteException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } finally {
            try {
                if (workbook != null)
                    workbook.close();
            } catch (Exception e) {
                logger.warn("IOException", e);
                SessionMessages.addErrorMessage(e.getMessage());
            }
        }
    }

    private WritableCellFormat headerExcel() {
        WritableFont fontCell = new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD, true);
        return new WritableCellFormat(fontCell);
    }

    private void exportRows(WritableSheet sheet, int i, TableForm.Row row) throws WriteException {
        int j = 0;
        for (Field field : row) {
            addFieldToCell(sheet, i, j, field);
            j++;
        }
    }

    private void addHeaderToReadSheet(WritableSheet sheet) throws WriteException {
        WritableCellFormat formatCell = headerExcel();
        int i = 0;
        for (FieldSet fieldset : form) {
            for (Field field : fieldset.fields()) {
                sheet.addCell(new jxl.write.Label(i, 0, field.getLabel(), formatCell));
                i++;
            }
        }
    }

    private void addHeaderToSearchSheet(WritableSheet sheet) throws WriteException {
        WritableCellFormat formatCell = headerExcel();
        int l = 0;
        for (TableForm.Column col : tableForm.getColumns()) {
            sheet.addCell(new jxl.write.Label(l, 0, col.getLabel(), formatCell));
            l++;
        }
    }

    private void addFieldToCell(WritableSheet sheet, int i, int j, Field field) throws WriteException {
        if (field instanceof NumericField) {
            NumericField numField = (NumericField) field;
            if (numField.getValue() != null) {
                Number number;
                BigDecimal decimalValue = numField.getValue();
                if (numField.getDecimalFormat() == null) {
                    number = new Number(j, i, decimalValue == null ? null : decimalValue.doubleValue());
                } else {
                    NumberFormat numberFormat = new NumberFormat(numField.getDecimalFormat().toPattern());
                    WritableCellFormat writeCellNumberFormat = new WritableCellFormat(numberFormat);
                    number = new Number(j, i, decimalValue == null ? null : decimalValue.doubleValue(),
                            writeCellNumberFormat);
                }
                sheet.addCell(number);
            }
        } else if (field instanceof PasswordField) {
            jxl.write.Label label = new jxl.write.Label(j, i, PasswordField.PASSWORD_PLACEHOLDER);
            sheet.addCell(label);
        } else if (field instanceof DateField) {
            DateField dateField = (DateField) field;
            DateTime dateCell;
            Date date = dateField.getValue();
            if (date != null) {
                DateFormat dateFormat = new DateFormat(dateField.getDatePattern());
                WritableCellFormat wDateFormat = new WritableCellFormat(dateFormat);
                dateCell = new DateTime(j, i, dateField.getValue() == null ? null : dateField.getValue(),
                        wDateFormat);
                sheet.addCell(dateCell);
            }
        } else {
            jxl.write.Label label = new jxl.write.Label(j, i, field.getStringValue());
            sheet.addCell(label);
        }
    }

    // **************************************************************************
    // exportSearchPdf
    // **************************************************************************

    @Button(list = "crud-search", key = "commons.exportPdf", order = 8, group = "crud", icon = " icon-share ")
    public Resolution exportSearchPdf() {
        try {
            // final File tmpFile = File.createTempFile(crudConfiguration.getName() + ".search", ".pdf");
            TempFileService fileService = TempFileService.getInstance();
            TempFile tempFile = fileService.newTempFile(MimeTypes.APPLICATION_PDF,
                    crudConfiguration.getSearchTitle() + ".pdf");
            OutputStream outputStream = tempFile.getOutputStream();
            exportSearchPdf(outputStream);
            outputStream.flush();
            outputStream.close();
            return fileService.stream(tempFile);
        } catch (Exception e) {
            logger.error("PDF export failed", e);
            SessionMessages.addErrorMessage(getMessage("commons.export.failed"));
            // return new RedirectResolution(getDispatch().getOriginalPath());
            return new RedirectResolution(getReturnUrl());
        }
    }

    public void exportSearchPdf(OutputStream outputStream) throws FOPException, IOException, TransformerException {

        setupSearchForm();

        loadObjects();

        setupTableForm4ExportAll(Mode.VIEW);

        FopFactory fopFactory = FopFactory.newInstance();
        // hongliangpan add
        try {
            String fopConfig = "fop.xml";
            String filePath = CrudAction4ItsProject.class.getClassLoader().getResource(fopConfig).getPath();
            fopFactory.setUserConfig(filePath);
            // String fonts = "/fonts"; 
            fopFactory.setFontBaseURL(new File(filePath).getParent());
        } catch (SAXException e) {
            logger.error(e.getMessage(), e);
        }
        InputStream xsltStream = null;
        try {
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, outputStream);

            xsltStream = getSearchPdfXsltStream();

            // Setup XSLT
            TransformerFactory Factory = TransformerFactory.newInstance();
            Transformer transformer = Factory.newTransformer(new StreamSource(xsltStream));

            // Set the value of a <param> in the stylesheet
            transformer.setParameter("versionParam", "2.0");

            // Setup input for XSLT transformation
            Reader reader = composeXmlSearch();
            Source src = new StreamSource(reader);

            // Resulting SAX events (the generated FO) must be piped through to
            // FOP
            Result res = new SAXResult(fop.getDefaultHandler());

            // Start XSLT transformation and FOP processing
            transformer.transform(src, res);
            reader.close();

            outputStream.flush();
        } finally {
            IOUtils.closeQuietly(xsltStream);
        }
    }

    /**
     * Returns a stream producing the contents of a XSLT document to produce the PDF export of the current search
     * results.
     */
    protected InputStream getSearchPdfXsltStream() {
        String templateFop = TEMPLATE_FOP_SEARCH;
        return getXsltStream(templateFop);
    }

    /**
     * Returns a XSLT stream by searching for a file first in this action's directory, then at the root of the
     * classpath.
     * 
     * @param templateFop the file to search for
     * @return the stream
     */
    protected InputStream getXsltStream(String templateFop) {
        // hongliangpan change dir ? webapp\WEB-INF\pages\t_customer  webapp\WEB-INF
        File dir = pageInstance.getDirectory();
        String rootPath = "WEB-INF";
        if (dir.getPath().indexOf(rootPath) > 0) {
            dir = new File(dir.getPath().substring(0, dir.getPath().indexOf(rootPath) + 8));
        }
        File fopFile = new File(dir, templateFop);
        if (fopFile.exists()) {
            logger.debug("Custom FOP template found: {}", fopFile);
            try {
                return new FileInputStream(fopFile);
            } catch (FileNotFoundException e) {
                throw new Error(e);
            }
        } else {
            logger.debug("Using default FOP template: {}", templateFop);
            ClassLoader cl = getClass().getClassLoader();
            return cl.getResourceAsStream(templateFop);
        }
    }

    /**
     * Composes an XML document representing the current search results.
     * 
     * @return
     * @throws IOException
     */
    protected Reader composeXmlSearch() throws IOException {
        XmlBuffer xb = new XmlBuffer();
        xb.writeXmlHeader("UTF-8");
        xb.openElement("class");
        xb.openElement("table");
        xb.write(crudConfiguration.getSearchTitle());
        xb.closeElement("table");

        double[] columnSizes = setupXmlSearchColumnSizes();

        for (double columnSize : columnSizes) {
            xb.openElement("column");
            xb.openElement("width");
            xb.write(columnSize + "em");
            xb.closeElement("width");
            xb.closeElement("column");
        }

        for (TableForm.Column col : tableForm.getColumns()) {
            xb.openElement("header");
            xb.openElement("nameColumn");
            xb.write(col.getLabel());
            xb.closeElement("nameColumn");
            xb.closeElement("header");
        }

        for (TableForm.Row row : tableForm.getRows()) {
            xb.openElement("rows");
            for (Field field : row) {
                xb.openElement("row");
                xb.openElement("value");
                xb.write(field.getStringValue());
                xb.closeElement("value");
                xb.closeElement("row");
            }
            xb.closeElement("rows");
        }

        xb.closeElement("class");

        return new StringReader(xb.toString());
    }

    /**
     * <p>
     * Returns an array of column sizes (in characters) for the search export.<br />
     * By default, sizes are computed comparing the relative sizes of each column, consisting of the header and the
     * values produced by the search.
     * </p>
     * <p>
     * Users can override this method to compute the sizes using a different algorithm, or hard-coding them for a
     * particular CRUD instance.
     * </p>
     */
    protected double[] setupXmlSearchColumnSizes() {
        double[] headerSizes = new double[tableForm.getColumns().length];
        for (int i = 0; i < headerSizes.length; i++) {
            TableForm.Column col = tableForm.getColumns()[i];
            int length = StringUtils.length(col.getLabel());
            headerSizes[i] = length;
        }

        double[] columnSizes = new double[tableForm.getColumns().length];
        for (TableForm.Row row : tableForm.getRows()) {
            int i = 0;
            for (Field field : row) {
                int size = StringUtils.length(field.getStringValue());
                double relativeSize = ((double) size) / tableForm.getRows().length;
                columnSizes[i++] += relativeSize;
            }
        }

        double totalSize = 0;
        for (int i = 0; i < columnSizes.length; i++) {
            double effectiveSize = Math.max(columnSizes[i], headerSizes[i]);
            columnSizes[i] = effectiveSize;
            totalSize += effectiveSize;
        }
        while (totalSize > 75) {
            int maxIndex = 0;
            double max = 0;
            for (int i = 0; i < columnSizes.length; i++) {
                if (columnSizes[i] > max) {
                    max = columnSizes[i];
                    maxIndex = i;
                }
            }
            columnSizes[maxIndex] -= 1;
            totalSize -= 1;
        }
        while (totalSize < 70) {
            int minIndex = 0;
            double min = Double.MAX_VALUE;
            for (int i = 0; i < columnSizes.length; i++) {
                if (columnSizes[i] < min) {
                    min = columnSizes[i];
                    minIndex = i;
                }
            }
            columnSizes[minIndex] += 1;
            totalSize += 1;
        }
        return columnSizes;
    }

    // **************************************************************************
    // ExportRead
    // **************************************************************************

    /**
     * Composes an XML document representing the current object.
     * 
     * @return
     * @throws IOException
     */
    protected Reader composeXmlPort() throws IOException, WriteException {
        setupSearchForm();

        loadObjects();

        setupTableForm(Mode.VIEW);
        setupForm(Mode.VIEW);
        form.readFromObject(object);

        XmlBuffer xb = new XmlBuffer();
        xb.writeXmlHeader("UTF-8");
        xb.openElement("class");
        xb.openElement("table");
        xb.write(crudConfiguration.getReadTitle());
        xb.closeElement("table");

        for (FieldSet fieldset : form) {
            xb.openElement("tableData");
            xb.openElement("rows");

            for (Field field : fieldset.fields()) {
                xb.openElement("row");
                xb.openElement("nameColumn");
                xb.write(field.getLabel());
                xb.closeElement("nameColumn");

                xb.openElement("value");
                xb.write(field.getStringValue());
                xb.closeElement("value");
                xb.closeElement("row");

            }
            xb.closeElement("rows");
            xb.closeElement("tableData");
        }

        xb.closeElement("class");

        return new StringReader(xb.toString());
    }

    public void exportReadPdf(File tempPdfFile) throws FOPException, IOException, TransformerException {
        setupSearchForm();

        loadObjects();

        setupTableForm(Mode.VIEW);

        FopFactory fopFactory = FopFactory.newInstance();

        FileOutputStream out = null;
        InputStream xsltStream = null;
        try {
            out = new FileOutputStream(tempPdfFile);

            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);

            xsltStream = getXsltStream(TEMPLATE_FOP_READ);

            // Setup XSLT
            TransformerFactory Factory = TransformerFactory.newInstance();
            Transformer transformer = Factory.newTransformer(new StreamSource(xsltStream));

            // Set the value of a <param> in the stylesheet
            transformer.setParameter("versionParam", "2.0");

            // Setup input for XSLT transformation
            Reader reader = composeXmlPort();
            Source src = new StreamSource(reader);

            // Resulting SAX events (the generated FO) must be piped through to
            // FOP
            Result res = new SAXResult(fop.getDefaultHandler());

            // Start XSLT transformation and FOP processing
            transformer.transform(src, res);

            reader.close();
            out.flush();
        } catch (Exception e) {
            logger.warn("IOException", e);
            SessionMessages.addErrorMessage(e.getMessage());
        } finally {
            IOUtils.closeQuietly(xsltStream);
            try {
                if (out != null)
                    out.close();
            } catch (Exception e) {
                logger.warn("IOException", e);
                SessionMessages.addErrorMessage(e.getMessage());
            }
        }
    }

    @Button(list = "crud-read", key = "commons.exportPdf", order = 8)
    public Resolution exportPdf() {
        try {
            final File tmpFile = File.createTempFile("export." + crudConfiguration.getName(), ".read.pdf");
            exportReadPdf(tmpFile);
            FileInputStream fileInputStream = new FileInputStream(tmpFile);
            return new StreamingResolution("application/pdf", fileInputStream) {
                @Override
                protected void stream(HttpServletResponse response) throws Exception {
                    super.stream(response);
                    if (!tmpFile.delete()) {
                        logger.warn("Temporary file {} could not be deleted", tmpFile.getAbsolutePath());
                    }
                }
            }.setFilename(crudConfiguration.getReadTitle() + ".pdf");
        } catch (Exception e) {
            logger.error("PDF export failed", e);
            SessionMessages.addErrorMessage(getMessage("commons.export.failed"));
            // return new RedirectResolution(getDispatch().getOriginalPath());
            return new RedirectResolution(getReturnUrl());

        }
    }
}