/*
* Copyright 2017 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.dmn.validation;
import org.drools.core.util.Drools;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.dmn.api.core.DMNCompiler;
import org.kie.dmn.api.core.DMNMessage;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.backend.marshalling.v1_1.DMNMarshallerFactory;
import org.kie.dmn.core.api.DMNMessageManager;
import org.kie.dmn.core.compiler.DMNCompilerImpl;
import org.kie.dmn.core.impl.DMNMessageImpl;
import org.kie.dmn.core.util.KieHelper;
import org.kie.dmn.core.util.DefaultDMNMessagesManager;
import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.model.v1_1.DMNModelInstrumentedBase;
import org.kie.dmn.model.v1_1.Definitions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.*;
import java.util.*;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static org.kie.dmn.validation.DMNValidator.Validation.VALIDATE_COMPILATION;
import static org.kie.dmn.validation.DMNValidator.Validation.VALIDATE_MODEL;
import static org.kie.dmn.validation.DMNValidator.Validation.VALIDATE_SCHEMA;
public class DMNValidatorImpl implements DMNValidator {
public static Logger LOG = LoggerFactory.getLogger(DMNValidatorImpl.class);
static Schema schema;
static {
try {
schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema();
} catch (SAXException e) {
e.printStackTrace();
}
}
/**
* A KieContainer is normally available,
* unless at runtime some problem prevented building it correctly.
*/
private Optional<KieContainer> kieContainer;
/**
* Collect at init time the runtime issues which prevented to build the `kieContainer` correctly.
*/
private List<DMNMessage> failedInitMsg;
public DMNValidatorImpl() {
final KieServices ks = KieServices.Factory.get();
final KieContainer kieContainer = KieHelper.getKieContainer(
ks.newReleaseId( "org.kie", "kie-dmn-validation", Drools.getFullVersion() ),
ks.getResources().newClassPathResource("dmn-validation-rules.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-auth-req.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-bkm.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-business-context.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-context.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-decision.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-dmnelementref.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-dtable.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-info-req.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-inputdata.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-know-req.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-know-source.drl", getClass() ),
ks.getResources().newClassPathResource("dmn-validation-rules-typeref.drl", getClass() ));
if( kieContainer != null ) {
this.kieContainer = Optional.of( kieContainer );
} else {
this.kieContainer = Optional.empty();
LOG.error("Unable to load embedded DMN validation rules file." );
String message = MsgUtil.createMessage( Msg.FAILED_VALIDATOR );
failedInitMsg.add(new DMNMessageImpl(DMNMessage.Severity.ERROR, message, Msg.FAILED_VALIDATOR.getType(), null ) );
}
}
public void dispose() {
kieContainer.ifPresent( KieContainer::dispose );
}
@Override
public List<DMNMessage> validate(Definitions dmnModel) {
return validate( dmnModel, VALIDATE_MODEL );
}
@Override
public List<DMNMessage> validate(Definitions dmnModel, Validation... options) {
DMNMessageManager results = new DefaultDMNMessagesManager();
EnumSet<Validation> flags = EnumSet.copyOf( Arrays.asList( options ) );
if( flags.contains( VALIDATE_SCHEMA ) ) {
MsgUtil.reportMessage( LOG,
DMNMessage.Severity.ERROR,
dmnModel,
results,
null,
null,
Msg.FAILED_NO_XML_SOURCE );
}
try {
validateModelCompilation( dmnModel, results, flags );
} catch ( Throwable t ) {
MsgUtil.reportMessage( LOG,
DMNMessage.Severity.ERROR,
dmnModel,
results,
t,
null,
Msg.FAILED_VALIDATOR );
}
return results.getMessages();
}
@Override
public List<DMNMessage> validate(File xmlFile) {
return validate( xmlFile, VALIDATE_MODEL );
}
@Override
public List<DMNMessage> validate(File xmlFile, Validation... options) {
DMNMessageManager results = new DefaultDMNMessagesManager( );
EnumSet<Validation> flags = EnumSet.copyOf( Arrays.asList( options ) );
if( flags.contains( VALIDATE_SCHEMA ) ) {
results.addAll( validateSchema( xmlFile ) );
}
if( flags.contains( VALIDATE_MODEL ) || flags.contains( VALIDATE_COMPILATION ) ) {
Definitions dmndefs = null;
try {
dmndefs = DMNMarshallerFactory.newDefaultMarshaller().unmarshal( new FileReader( xmlFile ) );
validateModelCompilation( dmndefs, results, flags );
} catch ( Throwable t ) {
MsgUtil.reportMessage( LOG,
DMNMessage.Severity.ERROR,
null,
results,
t,
null,
Msg.FAILED_VALIDATOR );
}
}
return results.getMessages();
}
@Override
public List<DMNMessage> validate(Reader reader) {
return validate( reader, VALIDATE_MODEL );
}
@Override
public List<DMNMessage> validate(Reader reader, Validation... options) {
DMNMessageManager results = new DefaultDMNMessagesManager( );
EnumSet<Validation> flags = EnumSet.copyOf( Arrays.asList( options ) );
try {
String content = readContent( reader );
if( flags.contains( VALIDATE_SCHEMA ) ) {
results.addAll( validateSchema( new StringReader( content ) ) );
}
if( flags.contains( VALIDATE_MODEL ) || flags.contains( VALIDATE_COMPILATION ) ) {
Definitions dmndefs = DMNMarshallerFactory.newDefaultMarshaller().unmarshal( new StringReader( content ) );
validateModelCompilation( dmndefs, results, flags );
}
} catch ( Throwable t ) {
MsgUtil.reportMessage( LOG,
DMNMessage.Severity.ERROR,
null,
results,
t,
null,
Msg.FAILED_VALIDATOR );
}
return results.getMessages();
}
private String readContent(Reader reader)
throws IOException {
char[] b = new char[32 * 1024];
StringBuilder content = new StringBuilder( );
int chars = -1;
while( (chars = reader.read( b ) ) > 0 ) {
content.append( b, 0, chars );
}
return content.toString();
}
private void validateModelCompilation(Definitions dmnModel, DMNMessageManager results, EnumSet<Validation> flags) {
if( flags.contains( VALIDATE_MODEL ) ) {
results.addAll( validateModel( dmnModel ) );
}
if( flags.contains( VALIDATE_COMPILATION ) ) {
results.addAll( validateCompilation( dmnModel, results ) );
}
}
private List<DMNMessage> validateSchema(File xmlFile) {
Source s = new StreamSource(xmlFile);
return validateSchema( s );
}
private List<DMNMessage> validateSchema(Reader reader) {
Source s = new StreamSource(reader);
return validateSchema( s );
}
private List<DMNMessage> validateSchema(Source s) {
List<DMNMessage> problems = new ArrayList<>();
try {
schema.newValidator().validate(s);
} catch (SAXException | IOException e) {
problems.add(new DMNMessageImpl( DMNMessage.Severity.ERROR, MsgUtil.createMessage( Msg.FAILED_XML_VALIDATION, e.getMessage() ), Msg.FAILED_XML_VALIDATION.getType(), null, e));
logDebugMessages( problems );
}
// TODO detect if the XSD is not provided through schemaLocation, and validate against embedded
return problems;
}
private List<DMNMessage> validateModel(Definitions dmnModel) {
if (!kieContainer.isPresent()) {
return failedInitMsg;
}
StatelessKieSession kieSession = kieContainer.get().newStatelessKieSession();
MessageReporter reporter = new MessageReporter();
kieSession.setGlobal( "reporter", reporter );
kieSession.execute(allChildren(dmnModel).collect(toList()));
return reporter.getMessages().getMessages();
}
private List<DMNMessage> validateCompilation(Definitions dmnModel, DMNMessageManager results) {
if( dmnModel != null ) {
DMNCompiler compiler = new DMNCompilerImpl();
DMNModel model = compiler.compile( dmnModel );
if( model != null ) {
return model.getMessages();
} else {
MsgUtil.reportMessage( LOG,
DMNMessage.Severity.ERROR,
dmnModel,
results,
null,
null,
Msg.FAILED_VALIDATOR );
}
}
return Collections.emptyList();
}
private static Stream<DMNModelInstrumentedBase> allChildren(DMNModelInstrumentedBase root) {
return Stream.concat( Stream.of(root),
root.getChildren().stream().flatMap(DMNValidatorImpl::allChildren) );
}
private void logDebugMessages(List<DMNMessage> messages) {
if ( LOG.isDebugEnabled() ) {
for ( DMNMessage m : messages ) {
LOG.debug("{}", m);
}
}
}
}