/*******************************************************************************
* Copyright 2017 Capital One Services, LLC and Bitwise, Inc.
* 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 hydrograph.ui.common.util;
import hydrograph.ui.common.Activator;
import hydrograph.ui.common.Messages;
import hydrograph.ui.common.component.config.Component;
import hydrograph.ui.common.component.config.Config;
import hydrograph.ui.common.component.config.Policy;
import hydrograph.ui.common.component.policyconfig.CategoryPolicies;
import hydrograph.ui.common.component.policyconfig.PolicyConfig;
import hydrograph.ui.logging.factory.LogFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.statushandlers.StatusManager;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Utility class to read and validate the xml configuration for following :
* <ul>
* <li>Component</li>
* <li>Policy</li>
* </ul>
* @author Bitwise
*/
public class XMLConfigUtil {
private Logger logger=LogFactory.INSTANCE.getLogger(XMLConfigUtil.class);
public static final XMLConfigUtil INSTANCE = new XMLConfigUtil();
private static HashMap<String, Component> map = new HashMap<>();
private static final String SEPARATOR = "/";
public final static String CONFIG_FILES_PATH = Platform.getInstallLocation().getURL().getPath() + Messages.XMLConfigUtil_CONFIG_FOLDER;
public final static String XML_CONFIG_FILES_PATH = Platform.getInstallLocation().getURL().getPath() + Messages.XMLConfigUtil_XML_CONFIG_FOLDER;
public final static String COMPONENT_CONFIG_XSD_PATH = Platform.getInstallLocation().getURL().getPath()+Messages.XMLConfigUtil_COMPONENTCONFIG_XSD_PATH;
public final static String POLICY_CONFIG_XSD_PATH = Platform.getInstallLocation().getURL().getPath()+Messages.XMLConfigUtil_POLICYCONFIG_XSD_PATH;
public final static List<Component> componentList = new ArrayList<>();
public PolicyConfig policyConfig ;
private XMLConfigUtil() {}
/** Reads the xml configuration files stored under the platform installation.
* These files contain the configuration required to create the component on UI.
* @return see {@link Component}
* @throws RuntimeException
* @throws IOException
* @throws SAXException
*/
public List<Component> getComponentConfig() throws RuntimeException, SAXException, IOException {
if(componentList != null && !componentList.isEmpty()){
return componentList;
}
else{
try{
JAXBContext jaxbContext = JAXBContext.newInstance(Config.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
String[] configFileList = getFilteredFiles(XML_CONFIG_FILES_PATH, getFileNameFilter(Messages.XMLConfigUtil_FILE_EXTENTION));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setExpandEntityReferences(false);
dbf.setFeature(Constants.DISALLOW_DOCTYPE_DECLARATION,true);
DocumentBuilder builder = dbf.newDocumentBuilder();
for (int i = 0; i < configFileList.length; i++){
logger.trace("Creating palette component: ", configFileList[i]);
if(validateXMLSchema(COMPONENT_CONFIG_XSD_PATH, XML_CONFIG_FILES_PATH + SEPARATOR + configFileList[i])){
Document document = builder.parse(new File(XML_CONFIG_FILES_PATH + SEPARATOR + configFileList[i]));
Config config = (Config) unmarshaller.unmarshal(document);
componentList.addAll(config.getComponent());
builder.reset();
}
}
validateAndFillComponentConfigList(componentList);
return componentList;
}catch(JAXBException | SAXException | IOException | ParserConfigurationException exception){
Status status = new Status(IStatus.ERROR,Activator.PLUGIN_ID, "XML read failed", exception);
StatusManager.getManager().handle(status, StatusManager.BLOCK);
logger.error(exception.getMessage());
throw new RuntimeException("Faild in reading XML Config files", exception); //$NON-NLS-1$
}
}
}
/**
* Gets the component.
*
* @param componentName
* the component name
* @return the component
*/
public Component getComponent(String componentName){
return map.get(componentName);
}
/** Filters out the files as per the applied filter and
* returns the file names array
* @param filePath directory location of the files
* @param filter criteria on which files are to be filtered
* @return
*/
public String[] getFilteredFiles(String filePath, FilenameFilter filter){
File file = new File(filePath);
String[] list = file.list(filter);
return (list == null) ? new String[0] : list;
}
/** Creates a file name filter in order to filter out only the required files.
* @param extention the files to be filtered on ex. .xml
* @return FilenameFilter
*/
public FilenameFilter getFileNameFilter(final String extention) {
FilenameFilter filenameFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if(name.lastIndexOf('.')>0)
{
// get last index for '.' char
int lastIndex = name.lastIndexOf('.');
// get extension
String str = name.substring(lastIndex);
// match path name extension
if(str.equals(extention))
{
return true;
}
}
return false;
}
};
return filenameFilter;
}
/**
* Gets the policy config.
*
* @return the policy config
* @throws RuntimeException
* the runtime exception
* @throws SAXException
* the SAX exception
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public PolicyConfig getPolicyConfig() throws RuntimeException, SAXException, IOException {
if(policyConfig !=null){
return policyConfig;
}
else{
try{
JAXBContext jaxbContext = JAXBContext.newInstance(PolicyConfig.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setExpandEntityReferences(false);
dbf.setFeature(Constants.DISALLOW_DOCTYPE_DECLARATION,true);
DocumentBuilder builder = dbf.newDocumentBuilder();
String[] configFileList = getFilteredFiles(CONFIG_FILES_PATH + SEPARATOR + Messages.XMLConfigUtil_POLICY, getFileNameFilter(Messages.XMLConfigUtil_FILE_EXTENTION));
for (int i = 0; i < configFileList.length; i++) {
if(validateXMLSchema(POLICY_CONFIG_XSD_PATH, CONFIG_FILES_PATH + SEPARATOR + Messages.XMLConfigUtil_POLICY + SEPARATOR + configFileList[i])) {
Document document = builder.parse(new File(CONFIG_FILES_PATH + SEPARATOR
+ Messages.XMLConfigUtil_POLICY + SEPARATOR + configFileList[i]));
policyConfig = (PolicyConfig) unmarshaller.unmarshal(document);
builder.reset();
}
}
return policyConfig;
}catch(JAXBException | SAXException | IOException | ParserConfigurationException exception){
Status status = new Status(IStatus.ERROR,Activator.PLUGIN_ID, "XML read failed", exception);
StatusManager.getManager().handle(status, StatusManager.BLOCK);
logger.error(exception.getMessage());
throw new RuntimeException("Faild in reading XML Config files", exception); //$NON-NLS-1$
}
}
}
/**
* Returns the collection of policies.This collection contains :<br>
* <ol>
* <li><b>Master policies :</b> applicable to all components</li>
* <li><b>Category policies : </b> applicable to category</li>
* <li><b>component policies : </b> applicable to component only</li>
* </ol>
* @param component
* @param componentName
* @return
* @throws IOException
* @throws SAXException
* @throws RuntimeException
*/
public List<Policy> getPoliciesForComponent(Component component) throws RuntimeException, SAXException, IOException {
List<Policy> policies = new ArrayList<>();
PolicyConfig policyConfig = XMLConfigUtil.INSTANCE.getPolicyConfig();
//put all master policies
policies.addAll(policyConfig.getMasterpolicies().getPolicy());
for (CategoryPolicies categoryPolicies : policyConfig.getCategorypolicies()) {
if (categoryPolicies.getCategory().toString().equalsIgnoreCase(component.getCategory().toString())) {
//put all category policies
policies.addAll(categoryPolicies.getPolicy());
//put all component policies
}
}
policies.addAll(component.getPolicy());
return policies;
}
/**
* Validates the xmls based on the provided XSD's constraints
* @param xsdPath
* @param xmlPath
* @return
* @throws Exception
* @throws SAXException
* @throws IOException
*/
public boolean validateXMLSchema(String xsdPath, String xmlPath) throws SAXException, IOException{
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema;
try {
schema = factory.newSchema(new File(xsdPath));
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new File(xmlPath)));
} catch (SAXException | IOException exception) {
logger.error(exception.getMessage());
throw exception;
}
return true;
}
private void validateAndFillComponentConfigList(List<Component> componentList) {
for (Component component : componentList) {
if(map.containsKey(component.getName())){
Status status = new Status(IStatus.ERROR,Activator.PLUGIN_ID,
"One or more configuration files have similar names, reconfigure the files", null);
StatusManager.getManager().handle(status, StatusManager.BLOCK);
//remove all component configuration from list
componentList.clear();
throw new RuntimeException("One or more Component names are similar");
}
map.put(component.getName(), component);
}
}
}