org.nuclos.server.report.ejb3.ReportFacadeBean.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.server.report.ejb3.ReportFacadeBean.java

Source

//Copyright (C) 2010  Novabit Informationssysteme GmbH
//
//This file is part of Nuclos.
//
//Nuclos is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Nuclos 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 Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with Nuclos.  If not, see <http://www.gnu.org/licenses/>.
package org.nuclos.server.report.ejb3;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.security.RolesAllowed;
import javax.print.DocFlavor;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.AttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.sql.DataSource;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperReport;

import org.apache.commons.lang.SerializationUtils;
import org.nuclos.common.NuclosEntity;
import org.nuclos.common.NuclosFatalException;
import org.nuclos.common.NuclosFile;
import org.nuclos.common.SearchConditionUtils;
import org.nuclos.common.UsageCriteria;
import org.nuclos.common.attribute.DynamicAttributeVO;
import org.nuclos.common.collect.collectable.CollectableEntityField;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparison;
import org.nuclos.common.collection.CollectionUtils;
import org.nuclos.common.collection.Transformer;
import org.nuclos.common.dal.vo.EntityObjectVO;
import org.nuclos.common.dal.vo.PivotInfo;
import org.nuclos.common.dal.vo.SystemFields;
import org.nuclos.common.dblayer.JoinType;
import org.nuclos.common.entityobject.CollectableEOEntityField;
import org.nuclos.common.genericobject.GenericObjectUtils;
import org.nuclos.common2.IdUtils;
import org.nuclos.common2.LangUtils;
import org.nuclos.common2.LocaleInfo;
import org.nuclos.common2.SpringLocaleDelegate;
import org.nuclos.common2.StringUtils;
import org.nuclos.common2.XMLUtils;
import org.nuclos.common2.exception.CommonBusinessException;
import org.nuclos.common2.exception.CommonCreateException;
import org.nuclos.common2.exception.CommonFatalException;
import org.nuclos.common2.exception.CommonFinderException;
import org.nuclos.common2.exception.CommonPermissionException;
import org.nuclos.common2.exception.CommonRemoveException;
import org.nuclos.common2.exception.CommonStaleVersionException;
import org.nuclos.server.common.AttributeCache;
import org.nuclos.server.common.MasterDataMetaCache;
import org.nuclos.server.common.MetaDataServerProvider;
import org.nuclos.server.common.NuclosSystemParameters;
import org.nuclos.server.common.SecurityCache;
import org.nuclos.server.common.ServerServiceLocator;
import org.nuclos.server.common.ejb3.LocaleFacadeLocal;
import org.nuclos.server.common.ejb3.NuclosFacadeBean;
import org.nuclos.server.dblayer.query.DbColumnExpression;
import org.nuclos.server.dblayer.query.DbCondition;
import org.nuclos.server.dblayer.query.DbFrom;
import org.nuclos.server.dblayer.query.DbQuery;
import org.nuclos.server.dblayer.query.DbQueryBuilder;
import org.nuclos.server.genericobject.ejb3.GenericObjectFacadeLocal;
import org.nuclos.server.genericobject.searchcondition.CollectableSearchExpression;
import org.nuclos.server.genericobject.valueobject.GenericObjectWithDependantsVO;
import org.nuclos.server.masterdata.MasterDataWrapper;
import org.nuclos.server.masterdata.ejb3.MasterDataFacadeLocal;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMap;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMapImpl;
import org.nuclos.server.masterdata.valueobject.MasterDataVO;
import org.nuclos.server.report.ByteArrayCarrier;
import org.nuclos.server.report.Export;
import org.nuclos.server.report.NuclosReportException;
import org.nuclos.server.report.NuclosReportPrintJob;
import org.nuclos.server.report.NuclosReportRemotePrintService;
import org.nuclos.server.report.ReportFieldDefinition;
import org.nuclos.server.report.ReportFieldDefinitionFactory;
import org.nuclos.server.report.api.JRNuclosDataSource;
import org.nuclos.server.report.export.CsvExport;
import org.nuclos.server.report.export.ExcelExport;
import org.nuclos.server.report.export.JasperExport;
import org.nuclos.server.report.valueobject.ReportOutputVO;
import org.nuclos.server.report.valueobject.ReportOutputVO.Format;
import org.nuclos.server.report.valueobject.ReportVO;
import org.nuclos.server.report.valueobject.ReportVO.ReportType;
import org.nuclos.server.report.valueobject.ResultColumnVO;
import org.nuclos.server.report.valueobject.ResultVO;
import org.nuclos.server.report.valueobject.SubreportVO;
import org.nuclos.server.ruleengine.NuclosBusinessRuleException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;

/**
 * Report facade encapsulating report management. <br>
 * <br>
 * Created by Novabit Informationssysteme GmbH <br>
 * Please visit <a href="http://www.novabit.de">www.novabit.de</a>
 */
@Transactional(noRollbackFor = { Exception.class })
public class ReportFacadeBean extends NuclosFacadeBean implements ReportFacadeRemote {

    public static final String ALIAS_INTID = "intid";

    private static final String CHARENCODING = "UTF-8";

    //

    private DataSource dataSource;

    private MasterDataFacadeLocal masterDataFacade;

    private GenericObjectFacadeLocal genericObjectFacade;

    public ReportFacadeBean() {
    }

    @Autowired
    @Qualifier("nuclos")
    void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Autowired
    final void setMasterDataFacade(MasterDataFacadeLocal masterDataFacade) {
        this.masterDataFacade = masterDataFacade;
    }

    private final MasterDataFacadeLocal getMasterDataFacade() {
        return masterDataFacade;
    }

    public GenericObjectFacadeLocal getGenericObjectFacade() {
        return genericObjectFacade;
    }

    @Autowired
    public void setGenericObjectFacade(GenericObjectFacadeLocal genericObjectFacade) {
        this.genericObjectFacade = genericObjectFacade;
    }

    @PostConstruct
    @RolesAllowed("Login")
    public void postConstruct() {
        // Determine classpath dynamically
        String jrClasspath = getClassPathFor(JasperReport.class, JRNuclosDataSource.class);
        info("Set JasperReports compile class-path to " + jrClasspath);
        System.setProperty("jasper.reports.compile.class.path", jrClasspath);
        System.setProperty("jasper.reports.compile.keep.java.file",
                NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILE_KEEP_JAVA_FILE));
        System.setProperty("jasper.reports.compile.temp",
                NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILE_TMP));
        // System.setProperty("jasper.reports.compiler.class",
        // NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILER_CLASS));
        System.setProperty("jasper.reports.compiler.class", JRJavaxToolsCompiler.class.getName());

        // Properties
        //
        // System.setProperty("jasper.reports.compile.class.path",
        // NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILE_CLASS_PATH));
        // System.setProperty("jasper.reports.compile.keep.java.file",
        // NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILE_KEEP_JAVA_FILE));
        // System.setProperty("jasper.reports.compile.temp",
        // NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILE_TMP));
        // System.setProperty("jasper.reports.compiler.class",
        // NuclosSystemParameters.getString(NuclosSystemParameters.JASPER_REPORTS_COMPILER_CLASS));

        // just to cache classes etc- @todo definetly a hack to load classes to improve performance generating a report for the first time after server restarts-
        Collection<MasterDataVO> repoCollection = getMasterDataFacade()
                .getMasterData(NuclosEntity.REPORTOUTPUT.getEntityName(), null, true);
        for (int i = 0; i < ReportOutputVO.Format.values().length; i++) {
            ReportOutputVO.Format format = ReportOutputVO.Format.values()[i];
            for (MasterDataVO mdVO : repoCollection) {
                try {
                    if (mdVO.getField("format").equals(format.name())) {
                        testReport(mdVO.getIntId());
                        break;
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
        }
    }

    /**
     * @return all reports
     * @throws CommonPermissionException
     */
    public Collection<ReportVO> getReports() throws CommonPermissionException {
        this.checkReadAllowed(NuclosEntity.REPORT);

        final Collection<ReportVO> collreport = new ArrayList<ReportVO>();

        for (MasterDataVO mdVO : getMasterDataFacade().getMasterData(NuclosEntity.REPORT.getEntityName(), null,
                true)) {
            Collection<Integer> readableReports = SecurityCache.getInstance()
                    .getReadableReports(getCurrentUserName()).get(ReportType.REPORT);
            if (readableReports == null) {
                return collreport;
            }
            if (readableReports.contains(mdVO.getIntId()))
                collreport.add(MasterDataWrapper.getReportVO(mdVO, getCurrentUserName()));
        }

        return collreport;
    }

    /**
     * Get all reports which have outputs containing the given datasourceId and
     * have the given type (report, form or template). We go a little
     * indirection so that we can use the security mechanism of the ReportBean.
     * 
     * @param iDataSourceId
     * @param iReportType
     * @return set of reports
     * @throws CommonPermissionException
     */
    public Collection<ReportVO> getReportsForDatasourceId(Integer iDataSourceId, final ReportType type)
            throws CommonPermissionException {
        this.checkReadAllowed(NuclosEntity.DATASOURCE);
        final Collection<ReportVO> collreport = new ArrayList<ReportVO>();

        DbQueryBuilder builder = dataBaseHelper.getDbAccess().getQueryBuilder();
        DbQuery<Integer> query = builder.createQuery(Integer.class);
        DbFrom r = query.from("T_UD_REPORT").alias("r");
        DbFrom o = r.join("T_UD_REPORTOUTPUT", JoinType.INNER).alias("o").on("INTID", "INTID_T_UD_REPORT",
                Integer.class);
        query.select(r.baseColumn("INTID", Integer.class));
        query.where(builder.and(builder.equal(r.baseColumn("INTTYPE", Integer.class), type.getValue()),
                builder.equal(o.baseColumn("INTID_T_UD_DATASOURCE", String.class), iDataSourceId)));

        for (Integer intid : dataBaseHelper.getDbAccess().executeQuery(query)) {
            try {
                MasterDataVO mdVO = getMasterDataFacade()
                        .get(type == ReportType.REPORT ? NuclosEntity.REPORT.getEntityName()
                                : NuclosEntity.FORM.getEntityName(), intid);
                Collection<Integer> readableReports = SecurityCache.getInstance()
                        .getReadableReports(getCurrentUserName()).get(type);
                if (mdVO != null && readableReports.contains(mdVO.getIntId()))
                    collreport.add(MasterDataWrapper.getReportVO(mdVO, getCurrentUserName()));
            } catch (CommonPermissionException ex) {
                throw new NuclosFatalException(ex);
            } catch (CommonFinderException ex) {
                // nothing found, do nothing
            }
        }

        return collreport;
    }

    /**
     * create new report
     * 
     * @param mdvo
     *            value object
     * @param mpDependants
     * @return new report
     */
    public MasterDataVO create(MasterDataVO mdvo, DependantMasterDataMap mpDependants) throws CommonCreateException,
            NuclosReportException, CommonPermissionException, NuclosBusinessRuleException {
        NuclosEntity entity = NuclosEntity.REPORT;
        if (ReportType.FORM.getValue().equals(mdvo.getField("type")))
            entity = NuclosEntity.FORM;

        this.checkReadAllowed(entity);
        final MasterDataVO result = getMasterDataFacade().create(entity.getEntityName(), mdvo, mpDependants, null);
        compileAndSaveAllXML(entity, result);
        SecurityCache.getInstance().invalidate();
        return result;
    }

    /**
     * modify an existing report
     * 
     * @param sEntity
     * @param mdvo
     *            value object
     * @param mpDependants
     * @return modified report
     */
    public Integer modify(MasterDataVO mdvo, DependantMasterDataMap mpDependants) throws CommonBusinessException {
        NuclosEntity entity = NuclosEntity.REPORT;
        if (ReportType.FORM.getValue().equals(mdvo.getField("type")))
            entity = NuclosEntity.FORM;

        this.checkReadAllowed(entity);
        final Integer result = (Integer) getMasterDataFacade().modify(entity.getEntityName(), mdvo, mpDependants,
                null);
        this.compileAndSaveAllXML(entity, mdvo);

        return result;
    }

    /**
     * delete an existing report
     * 
     * @param sEntity
     * @param mdvo
     *            value object
     */
    public void remove(MasterDataVO mdvo)
            throws CommonFinderException, CommonRemoveException, CommonStaleVersionException,
            CommonPermissionException, CommonCreateException, NuclosBusinessRuleException {
        NuclosEntity entity = NuclosEntity.REPORT;
        if (ReportType.FORM.getValue().equals(mdvo.getField("type")))
            entity = NuclosEntity.FORM;

        this.checkReadAllowed(entity);
        getMasterDataFacade().remove(entity.getEntityName(), mdvo, true, null);
        SecurityCache.getInstance().invalidate();
    }

    private void compileAndSaveAllXML(NuclosEntity entity, MasterDataVO mdvo) throws NuclosReportException {
        for (ReportOutputVO reportoutput : getReportOutputs(mdvo.getIntId())) {
            // Format is null when and only when it is the search output
            // template, which has to be PDF/XML
            if (reportoutput.getFormat() == null || "PDF".equals(reportoutput.getFormat().getValue())) {
                if (reportoutput.getSourceFile() != null) {
                    compileAndSaveXML(entity, reportoutput);

                    // subreports are only allowed for forms and reports
                    if (reportoutput.getFormat() != null) {
                        for (SubreportVO subreport : getSubreports(reportoutput.getId())) {
                            compileAndSaveXML(entity, subreport);
                        }
                    }
                } else {
                    // PDF must have a template
                    throw new NuclosReportException("report.error.missing.template.1");// "F\u00fcr eine PDF-Ausgabe muss eine Vorlage angegeben werden.");
                }
            } else {
                reportoutput.setReportCLS(null);
                // reportoutput.setSourceFileContent(null);
            }
        }
    }

    private void compileAndSaveXML(NuclosEntity entity, ReportOutputVO reportOutput) throws NuclosReportException {
        final String sReportXML;
        try {
            sReportXML = new String(reportOutput.getSourceFileContent().getData(), CHARENCODING);
        } catch (UnsupportedEncodingException ex) {
            throw new NuclosFatalException(ex);
        }
        final JasperReport jr = compileReport(sReportXML);
        reportOutput.setReportCLS(new ByteArrayCarrier(SerializationUtils.serialize(jr)));

        MasterDataVO mdvo = MasterDataWrapper.wrapReportOutputVO(reportOutput);

        try {
            getMasterDataFacade()
                    .modify(entity.equals(NuclosEntity.REPORT) ? NuclosEntity.REPORTOUTPUT.getEntityName()
                            : NuclosEntity.FORMOUTPUT.getEntityName(), mdvo, null, null);
        } catch (Exception e) {
            throw new NuclosReportException(e);
        }
    }

    private void compileAndSaveXML(NuclosEntity entity, SubreportVO subreport) throws NuclosReportException {
        final String sReportXML;
        try {
            sReportXML = new String(subreport.getSourcefileContent().getData(), CHARENCODING);
        } catch (UnsupportedEncodingException ex) {
            throw new NuclosFatalException(ex);
        }
        final JasperReport jr = compileReport(sReportXML);
        subreport.setReportCLS(new ByteArrayCarrier(SerializationUtils.serialize(jr)));

        MasterDataVO mdvo = MasterDataWrapper.wrapSubreportVO(entity, subreport);

        try {
            getMasterDataFacade().modify(entity.equals(NuclosEntity.REPORT) ? NuclosEntity.SUBREPORT.getEntityName()
                    : NuclosEntity.SUBFORM.getEntityName(), mdvo, null, null);
        } catch (Exception e) {
            throw new NuclosReportException(e);
        }
    }

    /**
     * compiles a report xml definition (jasperreports)
     * 
     * @param sReportXml
     *            report layout definition
     * @return compiled jasper report
     */
    private JasperReport compileReport(String sReportXml) {
        try {
            return JasperCompileManager.compileReport(
                    new ByteArrayInputStream(sReportXml.getBytes(XMLUtils.getXMLEncoding(sReportXml))));
        } catch (JRException ex) {
            throw new NuclosFatalException(ex);
        } catch (UnsupportedEncodingException ex) {
            throw new NuclosFatalException(ex);
        }
    }

    /**
     * get output formats for report
     * 
     * @param iReportId
     *            id of report
     * @return collection of output formats
     */
    public Collection<ReportOutputVO> getReportOutputs(Integer iReportId) {
        List<ReportOutputVO> outputs = new ArrayList<ReportOutputVO>();

        CollectableComparison cond = SearchConditionUtils.newMDReferenceComparison(
                MasterDataMetaCache.getInstance().getMetaData(NuclosEntity.REPORTOUTPUT), "parent", iReportId);
        Collection<MasterDataVO> mdOutputs = getMasterDataFacade()
                .getMasterData(NuclosEntity.REPORTOUTPUT.getEntityName(), cond, true);

        for (MasterDataVO mdVO : mdOutputs)
            outputs.add(MasterDataWrapper.getReportOutputVO(mdVO));

        return outputs;
    }

    public Collection<SubreportVO> getSubreports(Integer reportoutputId) {
        List<SubreportVO> subreports = new ArrayList<SubreportVO>();

        CollectableComparison cond = SearchConditionUtils.newMDReferenceComparison(
                MasterDataMetaCache.getInstance().getMetaData(NuclosEntity.SUBREPORT), "reportoutput",
                reportoutputId);
        Collection<MasterDataVO> mdSubreports = getMasterDataFacade()
                .getMasterData(NuclosEntity.SUBREPORT.getEntityName(), cond, true);

        for (MasterDataVO mdVO : mdSubreports) {
            subreports.add(new SubreportVO(mdVO));
        }

        return subreports;
    }

    /**
     * get output format for reportoutput id
     * 
     * @param iReportOutputId
     * @return reportoutput
     */
    public ReportOutputVO getReportOutput(Integer iReportOutputId)
            throws CommonFinderException, CommonPermissionException {
        return MasterDataWrapper.getReportOutputVO(
                getMasterDataFacade().get(NuclosEntity.REPORTOUTPUT.getEntityName(), iReportOutputId));
    }

    /**
     * finds reports (forms) by usage criteria
     * 
     * @param usagecriteria
     * @return collection of reports (forms)
     */
    @RolesAllowed("Login")
    public Collection<ReportVO> findReportsByUsage(UsageCriteria usagecriteria) {

        List<ReportVO> reports = new ArrayList<ReportVO>();

        DbQueryBuilder builder = dataBaseHelper.getDbAccess().getQueryBuilder();
        DbQuery<Integer> query = builder.createQuery(Integer.class);
        DbFrom t = query.from("T_UD_REPORTUSAGE").alias(SystemFields.BASE_ALIAS);
        query.select(t.baseColumn("INTID_T_UD_REPORT", Integer.class));
        DbCondition cond = builder.equal(t.baseColumn("INTID_T_MD_MODULE", Integer.class),
                usagecriteria.getModuleId());

        DbColumnExpression<Integer> cp = t.baseColumn("INTID_T_MD_PROCESS", Integer.class);
        final Integer iProcessId = usagecriteria.getProcessId();
        if (iProcessId == null) {
            query.where(builder.and(cond, cp.isNull()));
        } else {
            query.where(builder.and(cond, builder.or(cp.isNull(), builder.equal(cp, iProcessId))));
        }
        DbColumnExpression<Integer> cs = t.baseColumn("INTID_T_MD_STATE", Integer.class);
        final Integer iStatusId = usagecriteria.getStatusId();
        if (iStatusId == null) {
            query.addToWhereAsAnd(builder.and(cond, cs.isNull()));
        } else {
            query.addToWhereAsAnd(builder.and(cond, builder.or(cs.isNull(), builder.equal(cs, iStatusId))));
        }

        List<Integer> collUsableReportIds = dataBaseHelper.getDbAccess().executeQuery(query);

        Map<ReportType, Collection<Integer>> readableReports = SecurityCache.getInstance()
                .getReadableReports(getCurrentUserName());

        for (ReportType rt : readableReports.keySet()) {
            for (Integer reportId : CollectionUtils.intersection(collUsableReportIds, readableReports.get(rt))) {
                try {
                    reports.add(MasterDataWrapper.getReportVO(
                            getMasterDataFacade().get(NuclosEntity.FORM.getEntityName(), reportId),
                            getCurrentUserName()));
                } catch (CommonPermissionException ex) {
                    throw new CommonFatalException(ex);
                } catch (CommonFinderException ex) {
                    throw new CommonFatalException(ex);
                }
            }
        }

        Collections.sort(reports, new Comparator<ReportVO>() {

            @Override
            public int compare(ReportVO o1, ReportVO o2) {
                return StringUtils.emptyIfNull(o1.getName())
                        .compareToIgnoreCase(StringUtils.emptyIfNull(o2.getName()));
            }
        });

        return reports;
    }

    private static Export getExportInstance(ReportOutputVO.Format format) {
        switch (format) {
        case PDF:
            return new JasperExport();
        case CSV:
            return new CsvExport();
        case TSV:
            return new CsvExport('\t', 0, ' ', false);
        case XLS:
        case XLSX:
            return new ExcelExport(format);
        default:
            throw new NuclosFatalException("Format " + format + " is not supported for server-side execution.");
        }
    }

    @Override
    public NuclosFile testReport(Integer iReportOutputId) throws NuclosReportException {
        ReportOutputVO reportoutput;
        try {
            reportoutput = getReportOutput(iReportOutputId);
        } catch (CommonBusinessException e) {
            throw new NuclosReportException(e);
        }
        final Export export = getExportInstance(reportoutput.getFormat());
        return export.test(reportoutput);
    }

    /**
     * gets a report/form filled with data
     * 
     * @param iReportOutputId
     * @param mpParams
     *            parameters
     * @return report/form filled with data
     */
    public NuclosFile prepareReport(Integer iReportOutputId, Map<String, Object> mpParams, Integer iMaxRowCount)
            throws CommonFinderException, NuclosReportException, CommonPermissionException {
        final ReportOutputVO reportoutput = getReportOutput(iReportOutputId);
        final Locale locale = getLocale(reportoutput.getLocale(), SpringLocaleDelegate.getInstance().getLocale());
        final Export export = getExportInstance(reportoutput.getFormat());
        return export.export(reportoutput, mpParams, locale, LangUtils.defaultIfNull(iMaxRowCount, -1));
    }

    private Locale getLocale(String locale, Locale def) {
        if (!StringUtils.isNullOrEmpty(locale)) {
            for (LocaleInfo li : ServerServiceLocator.getInstance().getFacade(LocaleFacadeLocal.class)
                    .getAllLocales(true)) {
                if (locale.equalsIgnoreCase(li.name)) {
                    return li.toLocale();
                }
            }
        }
        return def;
    }

    /**
     * gets search result report filled with data
     * <p>
     * TODO: Don't serialize CollectableEntityField and/or CollectableEntity!
     * (tp) Refer to
     * {@link org.nuclos.common.CollectableEntityFieldWithEntity#readObject(ObjectInputStream)}
     * for details.
     * </p>
     * 
     * @param jrDesign
     *            prepared design template
     * @param clctexpr
     *            search expression
     * @param iModuleId
     *            module id of module to be displayed
     * @param bIncludeSubModules
     *            Include submodules in search?
     * @return search result report filled with data
     */
    @RolesAllowed("Login")
    public NuclosFile prepareSearchResult(CollectableSearchExpression clctexpr,
            List<? extends CollectableEntityField> lstclctefweSelected, Integer iModuleId,
            boolean bIncludeSubModules, ReportOutputVO.Format format, String customUsage)
            throws NuclosReportException {
        final String entity = MetaDataServerProvider.getInstance().getEntity(IdUtils.toLongId(iModuleId))
                .getEntity();
        final List<Integer> lstAttributeIds = GenericObjectUtils.getAttributeIds(lstclctefweSelected, entity,
                AttributeCache.getInstance());
        final Set<String> subentities = new HashSet<String>();
        for (CollectableEntityField cef : lstclctefweSelected) {
            if (!entity.equals(cef.getEntityName())) {
                subentities.add(cef.getEntityName());
            }
        }
        final List<GenericObjectWithDependantsVO> lstlowdcvo = getGenericObjectFacade()
                .getPrintableGenericObjectsWithDependants(iModuleId, clctexpr,
                        new HashSet<Integer>(lstAttributeIds), subentities, false, bIncludeSubModules, customUsage);
        final ResultVO resultvo = convertGenericObjectListToResultVO(entity, lstclctefweSelected, lstlowdcvo);
        final List<ReportFieldDefinition> fields = ReportFieldDefinitionFactory
                .getFieldDefinitions(lstclctefweSelected);
        final Export export = getExportInstance(format);
        return export.export(resultvo, fields);
    }

    /**
     * creates a new ResultVO object from a list of selected collectable entities.
     *
     * @param clcteMain main collectable entity
     * @param lstclctefweSelected List<CollectableEntityFieldWithEntity> attributes, subform or parent columns which are part of the content
     * @param lstlowdcvo List<GenericObjectWithDependantsVO> the data contained in the selected fields
     */
    private ResultVO convertGenericObjectListToResultVO(String sMainEntityName,
            List<? extends CollectableEntityField> lstclctefweSelected,
            List<GenericObjectWithDependantsVO> lstlowdcvo) {

        final ResultVO result = new ResultVO();

        // fill the columns:
        for (CollectableEntityField clctefwe : lstclctefweSelected) {
            final ResultColumnVO resultcolumnvo = new ResultColumnVO();
            resultcolumnvo.setColumnLabel(clctefwe.getLabel());
            resultcolumnvo.setColumnClassName(clctefwe.getJavaClass().getName());
            result.addColumn(resultcolumnvo);
        }

        // fill the rows:
        for (GenericObjectWithDependantsVO lowdcvo : lstlowdcvo) {
            final Object[] aoData = new Object[lstclctefweSelected.size()];

            int iColumn = 0;
            for (CollectableEntityField clctefwe : lstclctefweSelected) {
                final String sFieldName = clctefwe.getName();
                final String sFieldEntityName = clctefwe.getEntityName();

                if (sFieldEntityName.equals(sMainEntityName)) {
                    // own attribute:
                    final DynamicAttributeVO davo = lowdcvo.getAttribute(sFieldName, AttributeCache.getInstance());
                    aoData[iColumn] = davo != null ? davo.getValue() : null;
                } else {
                    final PivotInfo pinfo;
                    if (clctefwe instanceof CollectableEOEntityField) {
                        final CollectableEOEntityField f = (CollectableEOEntityField) clctefwe;
                        pinfo = f.getMeta().getPivotInfo();
                    } else {
                        pinfo = null;
                    }
                    // pivot field:
                    if (pinfo != null) {
                        final List<Object> values = new ArrayList<Object>(1);
                        final Collection<EntityObjectVO> items = lowdcvo.getDependants().getData(sFieldEntityName);

                        for (EntityObjectVO k : items) {
                            if (sFieldName.equals(k.getRealField(pinfo.getKeyField(), String.class))) {
                                values.add(k.getRealField(pinfo.getValueField(), pinfo.getValueType()));
                            }
                        }
                        if (values.isEmpty()) {
                            aoData[iColumn] = null;
                        } else {
                            assert values.size() == 1 : "Expected 1 value, got " + values;
                            aoData[iColumn] = values.get(0);
                        }
                    }
                    // subform field:
                    else {
                        final Collection<EntityObjectVO> collmdvo = lowdcvo.getDependants()
                                .getData(sFieldEntityName);
                        final List<Object> values = CollectionUtils.transform(collmdvo,
                                new Transformer<EntityObjectVO, Object>() {
                                    @Override
                                    public Object transform(EntityObjectVO i) {
                                        return i.getRealField(sFieldName);
                                    }
                                });
                        aoData[iColumn] = values;
                    }
                }

                iColumn++;
            }
            result.addRow(aoData);
        }

        return result;
    }

    @Override
    public NuclosFile prepareExport(ResultVO resultvo, Format format) throws NuclosReportException {
        final List<ReportFieldDefinition> fields = ReportFieldDefinitionFactory.getFieldDefinitions(resultvo);
        final Export export = getExportInstance(format);
        return export.export(resultvo, fields);
    }

    /**
     * @param iReportId
     *            report/form id
     * @return Is save allowed for the report/form with the given id?
     */
    @RolesAllowed("Login")
    public boolean isSaveAllowed(Integer iReportId) {
        return SecurityCache.getInstance().getWritableReportIds(getCurrentUserName()).contains(iReportId);
    }

    /**
     * finds reports readable for current user
     * 
     * @return collection of report ids
     */
    public Collection<Integer> getReadableReportIdsForCurrentUser() {
        Collection<Integer> result = new HashSet<Integer>();
        Map<ReportType, Collection<Integer>> readableReports = SecurityCache.getInstance()
                .getReadableReports(getCurrentUserName());
        for (ReportType rt : readableReports.keySet())
            result.addAll(readableReports.get(rt));
        return result;
    }

    public static String getClassPathFor(Class<?>... classes) {
        StringBuilder sb = new StringBuilder();
        for (Class<?> clazz : classes) {
            if (sb.length() > 0) {
                sb.append(File.pathSeparator);
            }
            try {
                ProtectionDomain protectionDomain = clazz.getProtectionDomain();
                CodeSource codeSource = protectionDomain.getCodeSource();
                URL location = codeSource.getLocation();
                String path = location.getFile();
                path = URLDecoder.decode(path, "UTF-8");
                sb.append(path);
            } catch (Exception e) {
                throw new NuclosFatalException("Cannot configure JasperReports classpath ", e);
            }
        }
        return sb.toString();
    }

    public NuclosReportRemotePrintService lookupDefaultPrintService() throws NuclosReportException {
        PrintService ps = PrintServiceLookup.lookupDefaultPrintService();
        if (ps == null) {
            String defprinter = System.getProperty("javax.print.defaultPrinter");
            if (defprinter != null) {
                PrintService[] prservices = PrintServiceLookup.lookupPrintServices(null, null);
                if (null == prservices || 0 >= prservices.length) {
                    throw new NuclosReportException(
                            "Es ist kein passender Print-Service installiert. " + defprinter); // @todo
                }

                for (int i = 0; i < prservices.length; i++) {
                    PrintService printService = prservices[i];
                    if (printService.getName().equals(defprinter)) {
                        return new NuclosReportRemotePrintService(printService);
                    }
                }
                throw new NuclosReportException(
                        "Es ist kein passender Default Print-Service installiert. " + defprinter); // @todo
            }
            throw new NuclosReportException("Es ist kein passender Default Print-Service installiert."); // @todo
        }
        return new NuclosReportRemotePrintService(ps);
    }

    public NuclosReportRemotePrintService[] lookupPrintServices(DocFlavor flavor, AttributeSet as)
            throws NuclosReportException {
        PrintService prservDflt = PrintServiceLookup.lookupDefaultPrintService();
        PrintService[] prservices = PrintServiceLookup.lookupPrintServices(flavor, as);
        if (null == prservices || 0 >= prservices.length) {
            if (null != prservDflt) {
                prservices = new PrintService[] { prservDflt };
            } else {
                throw new NuclosReportException("Es ist kein passender Print-Service installiert."); // @todo
            }
        }

        NuclosReportRemotePrintService[] rprservices = new NuclosReportRemotePrintService[prservices.length];
        for (int i = 0; i < prservices.length; i++) {
            rprservices[i] = new NuclosReportRemotePrintService(prservices[i]);
        }
        return rprservices;
    }

    public void printViaPrintService(NuclosReportRemotePrintService ps, NuclosReportPrintJob pj,
            PrintRequestAttributeSet aset, byte[] data) throws NuclosReportException {
        try {
            File prntFile = getFileFromBytes(data);
            pj.print(ps, prntFile.getAbsolutePath(), aset);
        } catch (Exception e) {
            throw new NuclosReportException(e.getMessage());
        }
    }

    private static File getFileFromBytes(byte[] data) throws IOException {
        File file = File.createTempFile("report_", ".tmp");
        file.deleteOnExit();

        OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
        try {
            os.write(data);
        } finally {
            os.close();
        }
        return file;
    }

}