/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.workbench.common.stunner.bpmn.backend.indexing;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.drools.compiler.builder.impl.KnowledgeBuilderConfigurationImpl;
import org.drools.core.io.impl.ByteArrayResource;
import org.drools.core.io.impl.ReaderResource;
import org.drools.core.xml.SemanticModules;
import org.guvnor.common.services.project.model.Package;
import org.guvnor.common.services.project.model.Project;
import org.jbpm.bpmn2.xml.BPMNDISemanticModule;
import org.jbpm.bpmn2.xml.BPMNExtensionsSemanticModule;
import org.jbpm.bpmn2.xml.BPMNSemanticModule;
import org.jbpm.compiler.xml.XmlProcessReader;
import org.jbpm.process.core.validation.ProcessValidationError;
import org.jbpm.process.core.validation.ProcessValidator;
import org.jbpm.process.core.validation.ProcessValidatorRegistry;
import org.kie.api.definition.process.Process;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderError;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.workbench.common.services.backend.project.ProjectClassLoaderHelper;
import org.kie.workbench.common.services.refactoring.backend.server.indexing.AbstractFileIndexer;
import org.kie.workbench.common.services.refactoring.backend.server.indexing.DefaultIndexBuilder;
import org.kie.workbench.common.services.shared.project.KieProject;
import org.kie.workbench.common.stunner.bpmn.resource.BPMNDefinitionSetResourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.backend.server.util.Paths;
import org.uberfire.java.nio.file.Path;
@ApplicationScoped
public class BpmnFileIndexer extends AbstractFileIndexer {
private static final Logger logger = LoggerFactory.getLogger(BpmnFileIndexer.class);
private static final SemanticModules modules = new SemanticModules();
static {
modules.addSemanticModule(new BPMNSemanticModule());
modules.addSemanticModule(new BPMNDISemanticModule());
modules.addSemanticModule(new BPMNExtensionsSemanticModule());
}
@Inject
protected BPMNDefinitionSetResourceType bpmnTypeDefinition;
@Inject
protected ProjectClassLoaderHelper classLoaderHelper;
@Override
public boolean supportsPath(Path path) {
return bpmnTypeDefinition.accept(Paths.convert(path));
}
/* (non-Javadoc)
* @see org.kie.workbench.common.services.refactoring.backend.server.indexing.AbstractFileIndexer#fillIndexBuilder(org.uberfire.java.nio.file.Path)
*/
@Override
protected DefaultIndexBuilder fillIndexBuilder(Path path) throws Exception {
final KieProject project = projectService.resolveProject(Paths.convert(path));
if (project == null) {
logger.error("Unable to index " + path.toUri().toString() + ": project could not be resolved.");
return null;
}
// responsible for basic index info: project name, branch, etc
final DefaultIndexBuilder builder = getIndexBuilder(path,
project);
String bpmnStr = ioService.readAllString(path);
ClassLoader projectClassLoader = getProjectClassLoader(project);
try {
List<BpmnProcessDataEventListener> processDataList = buildProcessDefinition(bpmnStr,
projectClassLoader);
if (processDataList != null) {
for (BpmnProcessDataEventListener processData : processDataList) {
addReferencedResourcesToIndexBuilder(builder,
processData);
builder.setPackageName(processData.getProcess().getPackageName());
}
}
} catch (Exception e) {
// log and ignore
logger.info("Indexing hampered because BPMN2 compilation failed [" + path.toString() + "]: " + e.getMessage());
}
/**
* IMPORTANT: sometimes the build of the BPMN2 might fail for minor reasons, including things like
* a bad script in a script task
*
* When this happens, we (re)parse the process definition, but do not completely "build" it
* (as in, what org.jbpm.compiler.ProcessBuilderImpl.buildProcess(Process, Resource) does).
*
*
* It *would* be more efficient to basically copy/paste the
* jbpm-flow-builder org.jbpm.compiler.ProcessBuilderImpl.addProcessFromXml(Resource) logic here,
* so that we do something like:
*
* 1. Use the XmlProcessReader to create a process
* 2. *try* to build the rest of the process (and fail safely if not)
* 3. do XmlProcessReader.getProcessBuildData().onBuildComplete(process)
* to complete collecting the information
*
* But... that's a high maintenance cost for this piece of software
*
* So until we can refactor the ProcessBuilderImpl logic (using functional logic for conditional handling?)
* to be used here, let's keep it simple (as in, parsing the BPMN2 a second time when the build fails..)
*/
// parse process definitions
XmlProcessReader processReader = new XmlProcessReader(modules,
projectClassLoader);
List<Process> processes = Collections.emptyList();
try {
processes = processReader.read(new StringReader(bpmnStr));
} catch (Exception e) {
logger.info("Unable to index because BPMN2 parsing failed [" + path.toString() + "]: " + e.getMessage());
}
// complete process definition processing
if (processes != null) {
for (Process process : processes) {
Resource resource = new ReaderResource(new StringReader(bpmnStr));
ProcessValidationError[] errors;
ProcessValidator validator = ProcessValidatorRegistry.getInstance().getValidator(process,
resource);
errors = validator.validateProcess(process);
if (errors.length > 0) {
logger.error("Trying to finish indexing process '" + process.getId() + "/" + process.getName() + "' despite " + errors.length + " validation errors.");
}
processReader.getProcessBuildData().onBuildComplete(process);
BpmnProcessDataEventListener helper = (BpmnProcessDataEventListener) process.getMetaData().get(BpmnProcessDataEventListener.NAME);
addReferencedResourcesToIndexBuilder(builder,
helper);
}
} else {
logger.warn("No process was found in file: " + path.toUri());
}
return builder;
}
// Protected method for testing
protected ClassLoader getProjectClassLoader(final KieProject project) {
return classLoaderHelper.getProjectClassLoader(project);
}
private List<BpmnProcessDataEventListener> buildProcessDefinition(String bpmn2Content,
ClassLoader projectClassLoader) throws IllegalArgumentException {
if (StringUtils.isEmpty(bpmn2Content)) {
return Collections.<BpmnProcessDataEventListener>emptyList();
}
// Set class loader
KnowledgeBuilder kbuilder = null;
if (projectClassLoader != null) {
KnowledgeBuilderConfigurationImpl pconf = new KnowledgeBuilderConfigurationImpl(projectClassLoader);
kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(pconf);
} else {
kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
}
// Build
kbuilder.add(new ByteArrayResource(bpmn2Content.getBytes()),
ResourceType.BPMN2);
if (kbuilder.hasErrors()) {
for (KnowledgeBuilderError error : kbuilder.getErrors()) {
logger.error("Error: {}",
error.getMessage());
}
logger.debug("Process Cannot be Parsed! \n {} \n",
bpmn2Content);
return Collections.<BpmnProcessDataEventListener>emptyList();
}
// Retrieve ProcessInfoHolder
List<BpmnProcessDataEventListener> processDataList = new ArrayList<>();
kbuilder.getKnowledgePackages().forEach(
pkg ->
pkg.getProcesses().forEach(
p -> {
BpmnProcessDataEventListener processData
= (BpmnProcessDataEventListener) p.getMetaData().get(BpmnProcessDataEventListener.NAME);
processDataList.add(processData);
})
);
return processDataList;
}
protected DefaultIndexBuilder getIndexBuilder(Path path,
Project project) {
final Package pkg = projectService.resolvePackage(Paths.convert(path));
if (pkg == null) {
logger.error("Unable to index " + path.toUri().toString() + ": package could not be resolved.");
return null;
}
// responsible for basic index info: project name, branch, etc
return new DefaultIndexBuilder(Paths.convert(path).getFileName(),
project,
pkg);
}
}