blob: bbc39259e5c127bd57144170093454fdb4f4cc51 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.geode.internal.config;
import static org.apache.geode.internal.lang.SystemProperty.getProperty;
import static org.apache.geode.internal.lang.SystemPropertyHelper.PACKAGES_TO_SCAN;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.apache.geode.cache.configuration.XSDRootElement;
import org.apache.geode.internal.classloader.ClassPathLoader;
import org.apache.geode.management.internal.util.ClasspathScanLoadHelper;
public class JAXBService {
private final Marshaller marshaller;
private final Unmarshaller unmarshaller;
private final NameSpaceFilter nameSpaceFilter;
public JAXBService(Class<?>... xsdRootClasses) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(xsdRootClasses);
marshaller = jaxbContext.createMarshaller();
unmarshaller = jaxbContext.createUnmarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
String schemas = Arrays.stream(xsdRootClasses).map(c -> {
XSDRootElement element = c.getAnnotation(XSDRootElement.class);
if (element != null && StringUtils.isNotEmpty(element.namespace())
&& StringUtils.isNotEmpty(element.schemaLocation())) {
return element.namespace() + " " + element.schemaLocation();
}
return null;
}).filter(Objects::nonNull).collect(Collectors.joining(" "));
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemas);
// use a custom Filter so that we can unmarshall older namespace or no namespace xml
XMLReader reader = XMLReaderFactory.createXMLReader();
nameSpaceFilter = new NameSpaceFilter();
nameSpaceFilter.setParent(reader);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static JAXBService create(Class<?>... xsdClasses) {
if (xsdClasses != null && xsdClasses.length > 0) {
return new JAXBService(xsdClasses);
}
// else, scan the classpath to find all the classes annotated with XSDRootElement
return new JAXBService(scanForClasses().toArray(new Class[0]));
}
public static JAXBService createWithValidation(Class<?>... xsdClasses) {
JAXBService jaxbService = create(xsdClasses);
jaxbService.validateWithLocalCacheXSD();
return jaxbService;
}
static Set<String> getPackagesToScan() {
Set<String> packages = new HashSet<>();
String sysProperty = getProperty(PACKAGES_TO_SCAN);
if (sysProperty != null) {
packages = Arrays.stream(sysProperty.split(",")).collect(Collectors.toSet());
} else {
packages.add("*");
}
return packages;
}
private static Set<Class<?>> scanForClasses() {
// scan the classpath to find all the classes annotated with XSDRootElement
Set<String> packages = getPackagesToScan();
try (ClasspathScanLoadHelper scanner = new ClasspathScanLoadHelper(packages)) {
return scanner.scanClasspathForAnnotation(XSDRootElement.class,
packages.toArray(new String[] {}));
}
}
private void validateWith(URL url) {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
Schema schema = factory.newSchema(url);
marshaller.setSchema(schema);
} catch (SAXException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public void validateWithLocalCacheXSD() {
// find the local Cache-1.0.xsd
URL local_cache_xsd = ClassPathLoader.getLatest()
.getResource("META-INF/schemas/geode.apache.org/schema/cache/cache-1.0.xsd");
validateWith(local_cache_xsd);
}
public String marshall(Object object) {
StringWriter sw = new StringWriter();
try {
sw.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
marshaller.marshal(object, sw);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
return sw.toString();
}
public <T> T unMarshall(String xml) {
try {
Source source = new SAXSource(nameSpaceFilter, new InputSource(new StringReader(xml)));
return (T) unmarshaller.unmarshal(source);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public <T> T unMarshall(String xml, Class<T> klass) {
try {
Source source = new SAXSource(nameSpaceFilter, new InputSource(new StringReader(xml)));
return unmarshaller.unmarshal(source, klass).getValue();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}