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

Java tutorial

Introduction

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

Source

package de.metas.ui.web.process;

import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.adempiere.exceptions.AdempiereException;
import org.adempiere.util.Services;
import org.adempiere.util.lang.IAutoCloseable;
import org.compiere.util.Env;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.UncheckedExecutionException;

import de.metas.printing.esb.base.util.Check;
import de.metas.process.IADPInstanceDAO;
import de.metas.process.ProcessDefaultParametersUpdater;
import de.metas.process.ProcessInfo;
import de.metas.ui.web.process.descriptor.ProcessDescriptor;
import de.metas.ui.web.process.descriptor.ProcessDescriptorsFactory;
import de.metas.ui.web.process.descriptor.ProcessParametersRepository;
import de.metas.ui.web.process.json.JSONCreateProcessInstanceRequest;
import de.metas.ui.web.view.IDocumentViewSelection;
import de.metas.ui.web.view.IDocumentViewsRepository;
import de.metas.ui.web.window.datatypes.DocumentId;
import de.metas.ui.web.window.datatypes.DocumentPath;
import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor;
import de.metas.ui.web.window.descriptor.factory.DocumentDescriptorFactory;
import de.metas.ui.web.window.model.Document;
import de.metas.ui.web.window.model.Document.CopyMode;

/*
 * #%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%
 */

@Component
public class ProcessInstancesRepository {
    //
    // Services
    @Autowired
    private ProcessDescriptorsFactory processDescriptorFactory;
    @Autowired
    private DocumentDescriptorFactory documentDescriptorFactory;
    @Autowired
    private IDocumentViewsRepository documentViewsRepo;

    private final LoadingCache<DocumentId, ProcessInstance> processInstances = CacheBuilder.newBuilder()
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .removalListener(new RemovalListener<DocumentId, ProcessInstance>() {
                @Override
                public void onRemoval(final RemovalNotification<DocumentId, ProcessInstance> notification) {
                    final ProcessInstance pinstance = notification.getValue();
                    pinstance.destroy();
                }
            }).build(CacheLoader.from(adPInstanceId -> retrieveProcessInstance(adPInstanceId)));

    public void cacheReset() {
        processInstances.invalidateAll();
        processInstances.cleanUp();
    }

    public ProcessDescriptor getProcessDescriptor(final int adProcessId) {
        return processDescriptorFactory.getProcessDescriptor(adProcessId);
    }

    public ProcessInstance createNewProcessInstance(final int adProcessId,
            final JSONCreateProcessInstanceRequest request) {
        //
        // Save process info together with it's parameters and get the the newly created AD_PInstance_ID
        final ProcessInfo processInfo = createProcessInfo(adProcessId, request);
        Services.get(IADPInstanceDAO.class).saveProcessInfo(processInfo);
        final DocumentId adPInstanceId = DocumentId.of(processInfo.getAD_PInstance_ID());

        //
        // Build the parameters document
        final ProcessDescriptor processDescriptor = getProcessDescriptor(adProcessId);
        final DocumentEntityDescriptor parametersDescriptor = processDescriptor.getParametersDescriptor();
        final Document parametersDoc = ProcessParametersRepository.instance
                .createNewParametersDocument(parametersDescriptor, adPInstanceId);

        // Set parameters's default values
        ProcessDefaultParametersUpdater.newInstance().addDefaultParametersProvider(processInfo)
                .onDefaultValue((parameter, value) -> parametersDoc.processValueChange(parameter.getColumnName(),
                        value, () -> "default parameter value"))
                .updateDefaultValue(parametersDoc.getFieldViews(), DocumentFieldAsProcessDefaultParameter::of);

        //
        // Create (webui) process instance and add it to our internal cache.
        final ProcessInstance pinstance = new ProcessInstance(processDescriptor, adPInstanceId, parametersDoc);
        processInstances.put(adPInstanceId, pinstance.copy(CopyMode.CheckInReadonly));
        return pinstance;
    }

    private ProcessInfo createProcessInfo(final int adProcessId, final JSONCreateProcessInstanceRequest request) {
        // Validate request's AD_Process_ID
        // (we are not using it, but just for consistency)
        if (request.getAD_Process_ID() > 0 && request.getAD_Process_ID() != adProcessId) {
            throw new IllegalArgumentException("Request's AD_Process_ID is not valid. It shall be " + adProcessId
                    + " or none but it was " + request.getAD_Process_ID());
        }

        Check.assume(adProcessId > 0, "adProcessId > 0");

        final String tableName;
        final int recordId;
        final String sqlWhereClause;

        //
        // View
        final String viewId = Strings.emptyToNull(request.getViewId());
        final String viewSelectedIdsAsStr;
        final DocumentPath singleDocumentPath = request.getSingleDocumentPath();
        if (!Check.isEmpty(viewId)) {
            final IDocumentViewSelection view = documentViewsRepo.getView(viewId);
            final Set<DocumentId> viewDocumentIds = request.getViewDocumentIds();
            viewSelectedIdsAsStr = DocumentId.toCommaSeparatedString(viewDocumentIds);
            final int view_AD_Window_ID = view.getAD_Window_ID();
            tableName = documentDescriptorFactory.getTableNameOrNull(view_AD_Window_ID);

            if (viewDocumentIds.size() == 1) {
                final DocumentId singleDocumentId = viewDocumentIds.iterator().next();
                recordId = singleDocumentId.toIntOr(-1);
            } else {
                recordId = -1;
            }

            sqlWhereClause = view.getSqlWhereClause(viewDocumentIds);
        }
        //
        // Single document call
        else if (singleDocumentPath != null) {
            viewSelectedIdsAsStr = null;
            if (singleDocumentPath.isRootDocument()) {
                tableName = documentDescriptorFactory.getTableNameOrNull(singleDocumentPath.getAD_Window_ID());
                recordId = singleDocumentPath.getDocumentId().toInt();
            } else {
                tableName = documentDescriptorFactory.getTableNameOrNull(singleDocumentPath.getAD_Window_ID(),
                        singleDocumentPath.getDetailId());
                recordId = singleDocumentPath.getSingleRowId().toInt();
            }
            sqlWhereClause = null;
        }
        //
        // From menu
        else {
            viewSelectedIdsAsStr = null;
            tableName = null;
            recordId = -1;
            sqlWhereClause = null;
        }

        return ProcessInfo.builder().setCtx(Env.getCtx()).setCreateTemporaryCtx().setAD_Process_ID(adProcessId)
                .setRecord(tableName, recordId).setWhereClause(sqlWhereClause)
                //
                .setLoadParametersFromDB(true) // important: we need to load the existing parameters from database, besides the internal ones we are adding here
                .addParameter(ProcessInstance.PARAM_ViewId, viewId) // internal parameter
                .addParameter(ProcessInstance.PARAM_ViewSelectedIds, viewSelectedIdsAsStr) // internal parameter
                //
                .build();
    }

    private ProcessInstance retrieveProcessInstance(final DocumentId adPInstanceId) {
        Check.assumeNotNull(adPInstanceId, "Parameter adPInstanceId is not null");
        Check.assume(adPInstanceId.toInt() > 0, "adPInstanceId > 0");

        //
        // Load process info
        final ProcessInfo processInfo = ProcessInfo.builder().setCtx(Env.getCtx()).setCreateTemporaryCtx()
                .setAD_PInstance_ID(adPInstanceId.toInt()).build();

        //
        // Build the parameters document
        final int adProcessId = processInfo.getAD_Process_ID();
        final ProcessDescriptor processDescriptor = getProcessDescriptor(adProcessId);

        //
        // Build the parameters (as document)
        final DocumentEntityDescriptor parametersDescriptor = processDescriptor.getParametersDescriptor();
        final Document parametersDoc = parametersDescriptor.getDataBinding().getDocumentsRepository()
                .retrieveDocumentById(parametersDescriptor, adPInstanceId);

        // TODO: handle the case when the process was already executed
        // In that case we need to load the result and provide it to ProcessInstance constructor

        return new ProcessInstance(processDescriptor, adPInstanceId, parametersDoc);
    }

    public <R> R forProcessInstanceReadonly(final int pinstanceIdAsInt,
            final Function<ProcessInstance, R> processor) {
        final DocumentId pinstanceId = DocumentId.of(pinstanceIdAsInt);

        try {
            try (final IAutoCloseable readLock = processInstances.get(pinstanceId).lockForReading()) {
                final ProcessInstance processInstance = processInstances.get(pinstanceId);
                return processor.apply(processInstance);
            }
        } catch (final UncheckedExecutionException | ExecutionException e) {
            throw AdempiereException.wrapIfNeeded(e);
        }
    }

    public <R> R forProcessInstanceWritable(final int pinstanceIdAsInt,
            final Function<ProcessInstance, R> processor) {
        final DocumentId pinstanceId = DocumentId.of(pinstanceIdAsInt);

        try {
            try (final IAutoCloseable writeLock = processInstances.get(pinstanceId).lockForWriting()) {
                final ProcessInstance processInstance = processInstances.get(pinstanceId)
                        .copy(CopyMode.CheckOutWritable);

                final R result = processor.apply(processInstance);

                // Actually put it back
                processInstance.saveIfValidAndHasChanges(false); // throwEx=false
                processInstances.put(pinstanceId, processInstance.copy(CopyMode.CheckInReadonly));

                return result;
            }
        } catch (final UncheckedExecutionException | ExecutionException e) {
            throw AdempiereException.wrapIfNeeded(e);
        }
    }
}