xml validation
This commit is contained in:
parent
781254121d
commit
0f687ae98f
|
@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
|
|||
import eu.dnetlib.is.resource.model.SimpleResource;
|
||||
import eu.dnetlib.is.resource.repository.SimpleResourceRepository;
|
||||
import eu.dnetlib.is.util.InformationServiceException;
|
||||
import eu.dnetlib.is.util.ResourceValidator;
|
||||
import eu.dnetlib.is.util.XmlIndenter;
|
||||
import eu.dnetlib.is.vocabulary.model.Synonym;
|
||||
import eu.dnetlib.is.vocabulary.model.Vocabulary;
|
||||
|
@ -35,6 +36,9 @@ public class OldProfilesImporter {
|
|||
@Autowired
|
||||
private VocabularyTermRepository vocabularyTermRepository;
|
||||
|
||||
@Autowired
|
||||
private ResourceValidator resourceValidator;
|
||||
|
||||
@Transactional
|
||||
public String importSimpleResource(final String xml) throws InformationServiceException {
|
||||
final SAXReader reader = new SAXReader();
|
||||
|
@ -92,6 +96,8 @@ public class OldProfilesImporter {
|
|||
throw new InformationServiceException("Invalid resource type: " + doc.valueOf("//RESOURCE_TYPE/@value"));
|
||||
}
|
||||
|
||||
resourceValidator.validate(res.getType(), resContent.trim());
|
||||
|
||||
simpleResourceRepository.save(res);
|
||||
simpleResourceRepository.setContentById(id, resContent.trim());
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import eu.dnetlib.is.resource.model.SimpleResource;
|
||||
import eu.dnetlib.is.resource.repository.SimpleResourceRepository;
|
||||
import eu.dnetlib.is.util.InformationServiceException;
|
||||
import eu.dnetlib.is.util.ResourceValidator;
|
||||
|
||||
@Service
|
||||
public class SimpleResourceService {
|
||||
|
@ -24,6 +25,9 @@ public class SimpleResourceService {
|
|||
@Autowired
|
||||
private SimpleResourceRepository simpleResourceRepository;
|
||||
|
||||
@Autowired
|
||||
private ResourceValidator resourceValidator;
|
||||
|
||||
private static final Log log = LogFactory.getLog(SimpleResourceService.class);
|
||||
|
||||
public SimpleResource getMetadata(final String id) throws InformationServiceException {
|
||||
|
@ -54,7 +58,10 @@ public class SimpleResourceService {
|
|||
public SimpleResource saveNewResource(final String name,
|
||||
final String type,
|
||||
final String description,
|
||||
final String content) {
|
||||
final String content) throws InformationServiceException {
|
||||
|
||||
resourceValidator.validate(type, content);
|
||||
|
||||
final Date now = new Date();
|
||||
|
||||
final SimpleResource res = new SimpleResource();
|
||||
|
@ -83,11 +90,11 @@ public class SimpleResourceService {
|
|||
|
||||
@Transactional
|
||||
public void saveContent(final String id, final String content) throws InformationServiceException {
|
||||
if (simpleResourceRepository.existsById(id)) {
|
||||
final SimpleResource res = simpleResourceRepository.findById(id).orElseThrow(() -> new InformationServiceException("Resource not found"));
|
||||
|
||||
resourceValidator.validate(res.getType(), content);
|
||||
|
||||
simpleResourceRepository.setContentById(id, content);
|
||||
} else {
|
||||
throw new InformationServiceException("Resource not found");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package eu.dnetlib.is.util;
|
||||
|
||||
public @interface JsonResource {
|
||||
|
||||
String value();
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package eu.dnetlib.is.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import eu.dnetlib.is.resource.model.ResourceType;
|
||||
import eu.dnetlib.is.resource.repository.ResourceTypeRepository;
|
||||
|
||||
@Component
|
||||
public class ResourceValidator {
|
||||
|
||||
private final Log log = LogFactory.getLog(ResourceValidator.class);
|
||||
|
||||
@Autowired
|
||||
private ResourceTypeRepository resourceTypeRepository;
|
||||
|
||||
public void validate(final String type, final String content) throws InformationServiceException {
|
||||
|
||||
final ResourceType rtype = resourceTypeRepository.findById(type).orElseThrow(() -> new InformationServiceException("Invalid type"));
|
||||
|
||||
if (rtype.getContentType().equals(MediaType.APPLICATION_XML_VALUE)) {
|
||||
validateXml(type, content);
|
||||
} else if (rtype.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
|
||||
validateJSON(type, content);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateXml(final String type, final String content) throws InformationServiceException {
|
||||
final Schema schema = getXmlSchema(type);
|
||||
if (schema == null) {
|
||||
log.warn("no registered schema for " + type);
|
||||
return;
|
||||
}
|
||||
final Validator validator = schema.newValidator();
|
||||
|
||||
try {
|
||||
validator.validate(new StreamSource(new StringReader(content)));
|
||||
} catch (final Exception e) {
|
||||
throw new InformationServiceException("invalid resource", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Schema getXmlSchema(final String type) {
|
||||
try {
|
||||
final InputStream is = getClass().getResourceAsStream("/schemas/" + type + ".xsd");
|
||||
if (is == null) { return null; }
|
||||
final String schemaSource = IOUtils.toString(is, StandardCharsets.UTF_8.name());
|
||||
if (StringUtils.isBlank(schemaSource)) { return null; }
|
||||
return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(new StringReader(schemaSource)));
|
||||
} catch (final SAXException | IOException e) {
|
||||
log.fatal("cannot parse resource type schema", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateJSON(final String type, final String content) throws InformationServiceException {
|
||||
final Class<?> clazz = getJsonClass(type);
|
||||
if (clazz == null) {
|
||||
log.warn("no registered Class for " + type);
|
||||
}
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
try {
|
||||
mapper.readValue(content, clazz);
|
||||
} catch (final Exception e) {
|
||||
throw new InformationServiceException("invalid resource", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getJsonClass(final String type) {
|
||||
final ClassLoader classLoader = ResourceValidator.class.getClassLoader();
|
||||
|
||||
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
|
||||
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(JsonResource.class));
|
||||
provider.setResourceLoader(new PathMatchingResourcePatternResolver(classLoader));
|
||||
|
||||
for (final BeanDefinition bd : provider.findCandidateComponents("eu.dnetlib")) {
|
||||
if (bd.getClass().getAnnotation(JsonResource.class).value().equals(type)) { return bd.getClass(); }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@ public class XmlIndenter {
|
|||
public static String indent(final Node node) {
|
||||
try {
|
||||
final StringWriter sw = new StringWriter();
|
||||
final XMLWriter writer = new XMLWriter(sw, new OutputFormat("\t", true));
|
||||
final XMLWriter writer = new XMLWriter(sw, OutputFormat.createPrettyPrint());
|
||||
writer.write(node);
|
||||
writer.close();
|
||||
return sw.toString();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
|
||||
<xs:element type="CLEANERRULESType" name="CLEANER_RULES" />
|
||||
|
||||
<xs:complexType name="CLEANERRULESType">
|
||||
<xs:sequence>
|
||||
<xs:element name="RULE" maxOccurs="unbounded" type="RULEType" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="RULEType">
|
||||
<xs:attribute type="xs:string" name="xpath" use="required" />
|
||||
<xs:attribute type="xs:string" name="vocabularies" use="optional" />
|
||||
<xs:attribute type="xs:string" name="groovy" use="optional" />
|
||||
<xs:attribute type="xs:boolean" name="strict" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,122 @@
|
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
<xs:element name="HADOOP_JOB">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="DESCRIPTION"/>
|
||||
<xs:element ref="STATIC_CONFIGURATION"/>
|
||||
<xs:element ref="JOB_INTERFACE"/>
|
||||
<xs:element ref="SCAN" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" use="required" type="xs:string"/>
|
||||
<xs:attribute name="type" use="required">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="mapreduce"/>
|
||||
<xs:enumeration value="oozie"/>
|
||||
<xs:enumeration value="admin"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="DESCRIPTION" type="xs:string"/>
|
||||
<xs:element name="STATIC_CONFIGURATION">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" ref="PROPERTY"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="PROPERTY">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="key" use="required" type="xs:NCName"/>
|
||||
<xs:attribute name="value" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="JOB_INTERFACE">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="PARAM"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="PARAM">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="description" use="required"/>
|
||||
<xs:attribute name="name" use="required" type="xs:NCName"/>
|
||||
<xs:attribute name="required" use="required" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="SCAN">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="FILTERS"/>
|
||||
<xs:element ref="FAMILIES"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="caching" use="optional" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="FILTERS">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="FILTER" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence><!-- The operator is OPTIONAL, when it is missing the default value is MUST_PASS_ALL -->
|
||||
<xs:attribute name="operator" use="optional" type="operatortype"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="FILTER">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="type" use="required" type="filtertype"/>
|
||||
<xs:attribute name="param" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="value" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="FAMILIES">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="FAMILY" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="FAMILY">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="param" use="optional" type="xs:string"/>
|
||||
<xs:attribute name="value" use="optional" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="STATUS">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="LAST_SUBMISSION_DATE"/>
|
||||
<xs:element ref="RUNNING_INSTANCES"/>
|
||||
<xs:element ref="CUMULATIVE_RUN"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="LAST_SUBMISSION_DATE">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="value" use="required" type="xs:dateTime"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="RUNNING_INSTANCES">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="value" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="CUMULATIVE_RUN">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="value" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="filtertype">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="prefix"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="operatortype">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="MUST_PASS_ALL"/>
|
||||
<xs:enumeration value="MUST_PASS_ONE"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
|
@ -49,6 +49,7 @@ app.controller('resourcesController', function($scope, $http) {
|
|||
'content' : r.content
|
||||
})).then(function successCallback(res) {
|
||||
alert("Resource saved");
|
||||
$('#newResourceModal').modal('hide');
|
||||
$scope.reload();
|
||||
}, function errorCallback(res) {
|
||||
alert('ERROR: ' + res.data.message);
|
||||
|
@ -71,6 +72,7 @@ app.controller('resourcesController', function($scope, $http) {
|
|||
'content' : content
|
||||
})).then(function successCallback(res) {
|
||||
alert("Resource saved");
|
||||
$('#editContentModal').modal('hide');
|
||||
}, function errorCallback(res) {
|
||||
alert('ERROR: ' + res.data.message);
|
||||
});
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" data-dismiss="modal" ng-click="createNewResource(tmpRes)" ng-disabled="!tmpRes.name || !tmpRes.content">Submit</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" ng-click="createNewResource(tmpRes)" ng-disabled="!tmpRes.name || !tmpRes.content">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -131,7 +131,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" data-dismiss="modal" ng-click="saveContent(tmpRes.id, tmpContent)" ng-disabled="!tmpContent">Submit</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" ng-click="saveContent(tmpRes.id, tmpContent)" ng-disabled="!tmpContent">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue