de.metas.ui.web.process.ProcessInstance.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.process.ProcessInstance.java

Source

package de.metas.ui.web.process;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import org.adempiere.ad.dao.IQueryBL;
import org.adempiere.ad.dao.IQueryFilter;
import org.adempiere.ad.trx.api.ITrx;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.model.RecordZoomWindowFinder;
import org.adempiere.util.Services;
import org.adempiere.util.lang.IAutoCloseable;
import org.adempiere.util.lang.impl.TableRecordReference;
import org.compiere.Adempiere;
import org.compiere.util.Env;
import org.compiere.util.MimeType;
import org.compiere.util.Util;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import com.google.common.base.MoreObjects;

import de.metas.adempiere.report.jasper.OutputType;
import de.metas.logging.LogManager;
import de.metas.printing.esb.base.util.Check;
import de.metas.process.JavaProcess;
import de.metas.process.ProcessExecutionResult;
import de.metas.process.ProcessExecutionResult.RecordsToOpen;
import de.metas.process.ProcessExecutor;
import de.metas.process.ProcessInfo;
import de.metas.ui.web.process.descriptor.ProcessDescriptor;
import de.metas.ui.web.process.exceptions.ProcessExecutionException;
import de.metas.ui.web.view.IDocumentViewSelection;
import de.metas.ui.web.view.IDocumentViewsRepository;
import de.metas.ui.web.view.json.JSONCreateDocumentViewRequest;
import de.metas.ui.web.view.json.JSONCreateDocumentViewRequest.JSONReferencing;
import de.metas.ui.web.window.datatypes.DocumentId;
import de.metas.ui.web.window.datatypes.DocumentPath;
import de.metas.ui.web.window.datatypes.DocumentType;
import de.metas.ui.web.window.datatypes.LookupValuesList;
import de.metas.ui.web.window.datatypes.json.JSONDocumentChangedEvent;
import de.metas.ui.web.window.datatypes.json.JSONViewDataType;
import de.metas.ui.web.window.model.Document;
import de.metas.ui.web.window.model.Document.CopyMode;
import de.metas.ui.web.window.model.DocumentSaveStatus;
import de.metas.ui.web.window.model.IDocumentChangesCollector.ReasonSupplier;
import de.metas.ui.web.window.model.IDocumentFieldView;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2016 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

/**
 * WEBUI Process instance.
 *
 * @author metas-dev <dev@metasfresh.com>
 *
 */
public final class ProcessInstance {
    private static final transient Logger logger = LogManager.getLogger(ProcessInstance.class);

    public static final String PARAM_ViewId = "$WEBUI_ViewId";
    public static final String PARAM_ViewSelectedIds = "$WEBUI_ViewSelectedIds";

    @Autowired
    @Lazy
    private IDocumentViewsRepository documentViewsRepo;

    private final DocumentId adPInstanceId;

    private final ProcessDescriptor processDescriptor;
    private final Document parameters;

    private boolean executed = false;
    private ProcessInstanceResult executionResult;

    private final ReentrantReadWriteLock readwriteLock;

    /** New instance constructor */
    /* package */ ProcessInstance(final ProcessDescriptor processDescriptor, final DocumentId adPInstanceId,
            final Document parameters) {
        super();
        Adempiere.autowire(this);

        this.processDescriptor = processDescriptor;
        this.adPInstanceId = adPInstanceId;
        this.parameters = parameters;

        executed = false;
        executionResult = null;

        readwriteLock = new ReentrantReadWriteLock();
    }

    /** Copy constructor */
    private ProcessInstance(final ProcessInstance from, final CopyMode copyMode) {
        super();

        // Adempiere.autowire(this);
        documentViewsRepo = from.documentViewsRepo;

        adPInstanceId = from.adPInstanceId;

        processDescriptor = from.processDescriptor;
        parameters = from.parameters.copy(copyMode);

        executed = from.executed;
        executionResult = from.executionResult;

        readwriteLock = from.readwriteLock; // always share
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).omitNullValues().add("AD_PInstance_ID", adPInstanceId)
                .add("executed", "executed").add("executionResult", executionResult)
                .add("processDescriptor", processDescriptor).toString();
    }

    public void destroy() {
        // TODO Auto-generated method stub

    }

    public ProcessDescriptor getDescriptor() {
        return processDescriptor;
    }

    public DocumentId getAD_PInstance_ID() {
        return adPInstanceId;
    }

    public Document getParametersDocument() {
        return parameters;
    }

    public Collection<IDocumentFieldView> getParameters() {
        return parameters.getFieldViews();
    }

    public ProcessInstance copy(final CopyMode copyMode) {
        return new ProcessInstance(this, copyMode);
    }

    public LookupValuesList getParameterLookupValues(final String parameterName) {
        return parameters.getFieldLookupValues(parameterName);
    }

    public LookupValuesList getParameterLookupValuesForQuery(final String parameterName, final String query) {
        return parameters.getFieldLookupValuesForQuery(parameterName, query);
    }

    public void processParameterValueChanges(final List<JSONDocumentChangedEvent> events,
            final ReasonSupplier reason) {
        assertNotExecuted();
        parameters.processValueChanges(events, reason);
    }

    /**
     * @return execution result or throws exception if the process was not already executed
     */
    public ProcessInstanceResult getExecutionResult() {
        final ProcessInstanceResult executionResult = this.executionResult;
        if (executionResult == null) {
            throw new AdempiereException("Process instance does not have an execution result yet: " + this);
        }
        return executionResult;
    }

    public boolean isExecuted() {
        return executed;
    }

    private final void assertNotExecuted() {
        if (isExecuted()) {
            throw new AdempiereException("Process already executed");
        }
    }

    public ProcessInstanceResult startProcess() {
        assertNotExecuted();

        //
        // Make sure it's saved in database
        if (!saveIfValidAndHasChanges(true)) {
            // shall not happen because the method throws the exception in case of failure
            throw new ProcessExecutionException("Instance could not be saved because it's not valid");
        }

        //
        // Create the process info and execute the process synchronously
        final Properties ctx = Env.getCtx(); // We assume the right context was already used when the process was loaded
        final ProcessDescriptor processDescriptor = getDescriptor();
        final String adLanguage = Env.getAD_Language(ctx);
        final String name = processDescriptor.getCaption(adLanguage);
        final ProcessExecutor processExecutor = ProcessInfo.builder().setCtx(ctx).setCreateTemporaryCtx()
                .setAD_PInstance_ID(getAD_PInstance_ID().toInt()).setTitle(name).setPrintPreview(true)
                .setJRDesiredOutputType(OutputType.PDF)
                //
                // Execute the process/report
                .buildAndPrepareExecution().executeSync();
        final ProcessExecutionResult processExecutionResult = processExecutor.getResult();

        //
        // Build and return the execution result
        {
            String summary = processExecutionResult.getSummary();
            if (Check.isEmpty(summary, true) || JavaProcess.MSG_OK.equals(summary)) {
                // hide summary if empty or MSG_OK (which is the most used non-message)
                summary = null;
            }

            final ProcessInstanceResult.Builder resultBuilder = ProcessInstanceResult.builder()
                    .setAD_PInstance_ID(processExecutionResult.getAD_PInstance_ID()).setSummary(summary)
                    .setError(processExecutionResult.isError());
            //
            // Result: report
            final File reportTempFile = saveReportToDiskIfAny(processExecutionResult);
            if (reportTempFile != null) {
                resultBuilder.setReportData(processExecutionResult.getReportFilename(),
                        processExecutionResult.getReportContentType(), reportTempFile);
            }

            //
            // Result: records to select
            updateRecordsToOpen(resultBuilder, processExecutor.getProcessInfo(),
                    processExecutionResult.getRecordsToOpen());

            //
            final ProcessInstanceResult result = resultBuilder.build();
            executionResult = result;
            if (result.isSuccess()) {
                executed = false;
            }
            return result;
        }
    }

    private static final File saveReportToDiskIfAny(final ProcessExecutionResult processExecutionResult) {
        //
        // If we are not dealing with a report, stop here
        final byte[] reportData = processExecutionResult.getReportData();
        if (reportData == null || reportData.length <= 0) {
            return null;
        }

        //
        // Create report temporary file
        File reportFile = null;
        try {
            final String reportFilePrefix = "report_" + processExecutionResult.getAD_PInstance_ID() + "_";

            final String reportContentType = processExecutionResult.getReportContentType();
            final String reportFileExtension = MimeType.getExtensionByType(reportContentType);
            final String reportFileSuffix = Check.isEmpty(reportFileExtension, true) ? ""
                    : "." + reportFileExtension.trim();
            reportFile = File.createTempFile(reportFilePrefix, reportFileSuffix);
        } catch (final IOException e) {
            throw new AdempiereException("Failed creating report temporary file " + reportFile);
        }

        //
        // Write report data to our temporary report file
        Util.writeBytes(reportFile, reportData);

        return reportFile;
    }

    private void updateRecordsToOpen(final ProcessInstanceResult.Builder resultBuilder,
            final ProcessInfo processInfo, final RecordsToOpen recordsToOpen) {
        if (recordsToOpen == null) {
            return;
        }
        //
        // View
        else if (recordsToOpen.isGridView()) {
            final IDocumentViewSelection view = createView(processInfo, recordsToOpen);
            resultBuilder.openView(view.getAD_Window_ID(), view.getViewId());
        }
        //
        // Single document
        else {
            final DocumentPath documentPath = extractSingleDocumentPath(recordsToOpen);
            resultBuilder.openSingleDocument(documentPath);
        }

    }

    private final IDocumentViewSelection createView(final ProcessInfo processInfo,
            final RecordsToOpen recordsToOpen) {
        final List<TableRecordReference> recordRefs = recordsToOpen.getRecords();
        if (recordRefs.isEmpty()) {
            return null; // shall not happen
        }

        final int adWindowId_Override = recordsToOpen.getAD_Window_ID(); // optional

        //
        // Create view create request builders from current records
        final Map<Integer, JSONCreateDocumentViewRequest.Builder> viewRequestBuilders = new HashMap<>();
        for (final TableRecordReference recordRef : recordRefs) {
            final int recordWindowId = adWindowId_Override > 0 ? adWindowId_Override
                    : RecordZoomWindowFinder.findAD_Window_ID(recordRef);
            final JSONCreateDocumentViewRequest.Builder viewRequestBuilder = viewRequestBuilders.computeIfAbsent(
                    recordWindowId,
                    key -> JSONCreateDocumentViewRequest.builder(recordWindowId, JSONViewDataType.grid));

            viewRequestBuilder.addFilterOnlyId(recordRef.getRecord_ID());
        }
        // If there is no view create request builder there stop here (shall not happen)
        if (viewRequestBuilders.isEmpty()) {
            return null;
        }

        //
        // Create the view create request from first builder that we have.
        if (viewRequestBuilders.size() > 1) {
            logger.warn("More than one views to be created found for {}. Creating only the first view.",
                    recordRefs);
        }
        final JSONCreateDocumentViewRequest viewRequest = viewRequestBuilders.values().iterator().next()
                .setReferencing(extractJSONReferencing(processInfo)).build();

        //
        // Create the view and set its ID to our process result.
        final IDocumentViewSelection view = documentViewsRepo.createView(viewRequest);
        return view;
    }

    private static final JSONReferencing extractJSONReferencing(final ProcessInfo processInfo) {
        final String tableName = processInfo.getTableNameOrNull();
        if (tableName == null) {
            return null;
        }
        final TableRecordReference sourceRecordRef = processInfo.getRecordRefOrNull();

        final IQueryFilter<Object> selectionQueryFilter = processInfo.getQueryFilterOrElse(null);
        if (selectionQueryFilter != null) {
            final int maxRecordAllowedToSelect = 200;
            final List<Integer> recordIds = Services.get(IQueryBL.class)
                    .createQueryBuilder(tableName, Env.getCtx(), ITrx.TRXNAME_ThreadInherited)
                    .filter(selectionQueryFilter).setLimit(maxRecordAllowedToSelect + 1).create().listIds();
            if (recordIds.isEmpty()) {
                return null;
            } else if (recordIds.size() > maxRecordAllowedToSelect) {
                throw new AdempiereException(
                        "Selecting more than " + maxRecordAllowedToSelect + " records is not allowed");
            }

            final TableRecordReference firstRecordRef = TableRecordReference.of(tableName, recordIds.get(0));
            final int adWindowId = RecordZoomWindowFinder.findAD_Window_ID(firstRecordRef); // assume all records are from same window
            return JSONReferencing.of(adWindowId, recordIds);
        } else if (sourceRecordRef != null) {
            final int adWindowId = RecordZoomWindowFinder.findAD_Window_ID(sourceRecordRef);
            return JSONReferencing.of(adWindowId, sourceRecordRef.getRecord_ID());
        } else {
            return null;
        }
    }

    private static final DocumentPath extractSingleDocumentPath(final RecordsToOpen recordsToOpen) {
        final TableRecordReference recordRef = recordsToOpen.getSingleRecord();
        final int documentId = recordRef.getRecord_ID();

        int adWindowId = recordsToOpen.getAD_Window_ID();
        if (adWindowId <= 0) {
            adWindowId = RecordZoomWindowFinder.findAD_Window_ID(recordRef);
        }

        return DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId, documentId);
    }

    /**
     * Save
     *
     * @param throwEx <code>true</code> if we shall throw an exception if document it's not valid or it's not saved
     * @return true if valid and saved
     */
    public boolean saveIfValidAndHasChanges(final boolean throwEx) {
        final Document parametersDocument = getParametersDocument();
        final DocumentSaveStatus parametersSaveStatus = parametersDocument.saveIfValidAndHasChanges();
        final boolean saved;
        if (parametersSaveStatus.hasChangesToBeSaved() || parametersSaveStatus.isError()) {
            saved = false;
            if (throwEx) {
                throw new ProcessExecutionException(parametersSaveStatus.getReason());
            }
        } else {
            saved = true;
        }

        return saved;
    }

    public IAutoCloseable lockForReading() {
        final ReadLock readLock = readwriteLock.readLock();
        logger.debug("Acquiring read lock for {}: {}", this, readLock);
        readLock.lock();
        logger.debug("Acquired read lock for {}: {}", this, readLock);

        return () -> {
            readLock.unlock();
            logger.debug("Released read lock for {}: {}", this, readLock);
        };
    }

    public IAutoCloseable lockForWriting() {
        final WriteLock writeLock = readwriteLock.writeLock();
        logger.debug("Acquiring write lock for {}: {}", this, writeLock);
        writeLock.lock();
        logger.debug("Acquired write lock for {}: {}", this, writeLock);

        return () -> {
            writeLock.unlock();
            logger.debug("Released write lock for {}: {}", this, writeLock);
        };
    }
}