com.github.zhanhb.ckfinder.connector.support.XmlConfigurationParser.java Source code

Java tutorial

Introduction

Here is the source code for com.github.zhanhb.ckfinder.connector.support.XmlConfigurationParser.java

Source

package com.github.zhanhb.ckfinder.connector.support;

import com.github.zhanhb.ckfinder.connector.api.AccessControl;
import com.github.zhanhb.ckfinder.connector.api.BasePathBuilder;
import com.github.zhanhb.ckfinder.connector.api.ConnectorException;
import com.github.zhanhb.ckfinder.connector.api.Constants;
import com.github.zhanhb.ckfinder.connector.api.ErrorCode;
import com.github.zhanhb.ckfinder.connector.api.ImageProperties;
import com.github.zhanhb.ckfinder.connector.api.License;
import com.github.zhanhb.ckfinder.connector.api.ResourceType;
import com.github.zhanhb.ckfinder.connector.api.ThumbnailProperties;
import com.github.zhanhb.ckfinder.connector.plugins.FileEditorPlugin;
import com.github.zhanhb.ckfinder.connector.plugins.ImageResizeParam;
import com.github.zhanhb.ckfinder.connector.plugins.ImageResizePlugin;
import com.github.zhanhb.ckfinder.connector.plugins.ImageResizeSize;
import com.github.zhanhb.ckfinder.connector.plugins.WatermarkPlugin;
import com.github.zhanhb.ckfinder.connector.plugins.WatermarkSettings;
import com.github.zhanhb.ckfinder.connector.utils.PathUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Class loads configuration from XML file.
 *
 * @author zhanhb
 */
public enum XmlConfigurationParser {
    INSTANCE;

    /**
     * bytes in KB.
     */
    private static final int BYTES = 1024;

    private static final float MAX_QUALITY = 100f;

    /**
     *
     * @param resourceLoader resource loader to load xml configuration and
     * watermark resource
     * @param basePathBuilder base url and path builder
     * @param xmlFilePath string representation of the xml file
     * @return parsed configuration
     * @throws java.lang.Exception if exception occur when parse the resource
     */
    public DefaultCKFinderContext parse(ResourceLoader resourceLoader, BasePathBuilder basePathBuilder,
            String xmlFilePath) throws Exception {
        DefaultCKFinderContext.Builder builder = DefaultCKFinderContext.builder();
        Path basePath = basePathBuilder.getBasePath();
        init(builder, resourceLoader, xmlFilePath, basePath, basePathBuilder);
        return builder.build();
    }

    /**
     * Initializes configuration from XML file.
     *
     * @param builder context builder
     * @param basePathBuilder base url and path builder
     * @param resourceLoader resource loader to load xml configuration
     * @param basePath base path
     * @param xmlFilePath string representation of the xml file
     * @throws ConnectorException when error occurs
     * @throws IOException when IO Exception occurs.
     * @throws org.xml.sax.SAXException syntax error in xml file
     * @throws ParserConfigurationException no xml provider is avaliable
     */
    private void init(DefaultCKFinderContext.Builder builder, ResourceLoader resourceLoader, String xmlFilePath,
            Path basePath, BasePathBuilder basePathBuilder)
            throws ConnectorException, IOException, ParserConfigurationException, SAXException {
        Resource resource = getFullConfigPath(resourceLoader, xmlFilePath);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc;
        try (InputStream stream = resource.getInputStream()) {
            doc = db.parse(stream);
        }
        doc.normalize();
        License.Builder licenseBuilder = License.builder().name("").key("");
        Node node = doc.getFirstChild();
        ThumbnailProperties thumbnail = null;
        ImageProperties.Builder image = ImageProperties.builder();
        if (node != null) {
            NodeList nodeList = node.getChildNodes();
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node childNode = nodeList.item(i);
                switch (childNode.getNodeName()) {
                case "enabled":
                    builder.enabled(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "licenseName":
                    licenseBuilder.name(nullNodeToString(childNode));
                    break;
                case "licenseKey":
                    licenseBuilder.key(nullNodeToString(childNode));
                    break;
                case "imgWidth":
                    String width = nullNodeToString(childNode);
                    width = width.replaceAll("\\D", "");
                    try {
                        image.maxWidth(Integer.parseInt(width));
                    } catch (NumberFormatException e) {
                        image.maxWidth(Constants.DEFAULT_IMG_WIDTH);
                    }
                    break;
                case "imgQuality":
                    String quality = nullNodeToString(childNode);
                    quality = quality.replaceAll("\\D", "");
                    image.quality(adjustQuality(quality));
                    break;
                case "imgHeight":
                    String height = nullNodeToString(childNode);
                    height = height.replaceAll("\\D", "");
                    try {
                        image.maxHeight(Integer.parseInt(height));
                    } catch (NumberFormatException e) {
                        image.maxHeight(Constants.DEFAULT_IMG_HEIGHT);
                    }
                    break;
                case "thumbs":
                    thumbnail = createThumbs(childNode.getChildNodes(), basePath, basePathBuilder);
                    break;
                case "accessControls":
                    setACLs(builder, childNode.getChildNodes());
                    break;
                case "hideFolders":
                    setHiddenFolders(builder, childNode.getChildNodes());
                    break;
                case "hideFiles":
                    setHiddenFiles(builder, childNode.getChildNodes());
                    break;
                case "checkDoubleExtension":
                    builder.checkDoubleFileExtensions(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "disallowUnsafeCharacters":
                    builder.disallowUnsafeCharacters(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "forceASCII":
                    builder.forceAscii(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "checkSizeAfterScaling":
                    builder.checkSizeAfterScaling(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "htmlExtensions":
                    String htmlExt = nullNodeToString(childNode);
                    StringTokenizer scanner = new StringTokenizer(htmlExt, ",");
                    while (scanner.hasMoreTokens()) {
                        String val = scanner.nextToken();
                        if (val != null && !val.isEmpty()) {
                            builder.htmlExtension(val.trim().toLowerCase());
                        }
                    }
                    break;
                case "secureImageUploads":
                    builder.secureImageUploads(Boolean.parseBoolean(nullNodeToString(childNode)));
                    break;
                case "uriEncoding":
                    break;
                case "userRoleSessionVar":
                    builder.userRoleName(nullNodeToString(childNode));
                    break;
                case "defaultResourceTypes":
                    String value = nullNodeToString(childNode);
                    StringTokenizer sc = new StringTokenizer(value, ",");
                    while (sc.hasMoreTokens()) {
                        builder.defaultResourceType(sc.nextToken());
                    }
                    break;
                case "plugins":
                    setPlugins(builder, childNode, resourceLoader);
                    break;
                default:
                    break;
                }
            }
        }
        builder.image(image.build());
        builder.licenseFactory(new FixLicenseFactory(licenseBuilder.build()));
        setTypes(builder.thumbnail(thumbnail), doc, basePathBuilder, thumbnail);
    }

    /**
     * Returns XML node contents or empty String instead of null if XML node is
     * empty.
     *
     * @param childNode the xml node
     * @return the text content
     */
    private String nullNodeToString(Node childNode) {
        String textContent = childNode.getTextContent();
        return textContent == null ? "" : textContent.trim();
    }

    /**
     * Gets absolute path to XML configuration file.
     *
     * @param resourceLoader resource loader to load xml configuration and
     * watermark resource
     * @param xmlFilePath string representation of the xml file
     * @return absolute path to XML configuration file
     * @throws ConnectorException when absolute path cannot be obtained.
     */
    private Resource getFullConfigPath(ResourceLoader resourceLoader, String xmlFilePath)
            throws ConnectorException {
        Resource resource = resourceLoader.getResource(xmlFilePath);
        if (!resource.exists()) {
            throw new ConnectorException(ErrorCode.FILE_NOT_FOUND,
                    "Configuration file could not be found under specified location.");
        }
        return resource;
    }

    /**
     * Adjusts image quality.
     *
     * @param imgQuality Image quality
     * @return Adjusted image quality
     */
    private float adjustQuality(String imgQuality) {
        float helper;
        try {
            helper = Math.abs(Float.parseFloat(imgQuality));
        } catch (NumberFormatException e) {
            return Constants.DEFAULT_IMG_QUALITY;
        }
        if (helper == 0 || helper == 1) {
            return helper;
        } else if (helper > 0 && helper < 1) {
            helper = (Math.round(helper * MAX_QUALITY) / MAX_QUALITY);
        } else if (helper > 1 && helper <= MAX_QUALITY) {
            helper = (Math.round(helper) / MAX_QUALITY);
        } else {
            helper = Constants.DEFAULT_IMG_QUALITY;
        }
        return helper;
    }

    /**
     * Sets hidden files list defined in XML configuration.
     *
     * @param builder context builder
     * @param childNodes list of files nodes.
     */
    private void setHiddenFiles(DefaultCKFinderContext.Builder builder, NodeList childNodes) {
        for (int i = 0, j = childNodes.getLength(); i < j; i++) {
            Node node = childNodes.item(i);
            if (node.getNodeName().equals("file")) {
                String val = nullNodeToString(node);
                if (!val.isEmpty()) {
                    builder.hiddenFile(val.trim());
                }
            }
        }
    }

    /**
     * Sets hidden folders list defined in XML configuration.
     *
     * @param builder context builder
     * @param childNodes list of folder nodes.
     */
    private void setHiddenFolders(DefaultCKFinderContext.Builder builder, NodeList childNodes) {
        for (int i = 0, j = childNodes.getLength(); i < j; i++) {
            Node node = childNodes.item(i);
            if (node.getNodeName().equals("folder")) {
                String val = nullNodeToString(node);
                if (!val.isEmpty()) {
                    builder.hiddenFolder(val.trim());
                }
            }
        }
    }

    /**
     * Sets ACL configuration as a list of access control levels.
     *
     * @param builder context builder
     * @param childNodes nodes with ACL configuration.
     */
    private void setACLs(DefaultCKFinderContext.Builder builder, NodeList childNodes) {
        InMemoryAccessController accessControl = new InMemoryAccessController();
        for (int i = 0, j = childNodes.getLength(); i < j; i++) {
            Node childNode = childNodes.item(i);
            if (childNode.getNodeName().equals("accessControl")) {
                AccessControlLevel acl = getACLFromNode(childNode);
                if (acl != null) {
                    accessControl.addPermission(acl);
                }
            }
        }
        builder.accessControl(accessControl);
    }

    /**
     * Gets single ACL configuration from XML node.
     *
     * @param childNode XML accessControl node.
     * @return access control level object.
     */
    private AccessControlLevel getACLFromNode(Node childNode) {
        String role = null;
        String resourceType = null;
        String folder = null;
        int mask = 0;
        for (int i = 0, j = childNode.getChildNodes().getLength(); i < j; i++) {
            Node childChildNode = childNode.getChildNodes().item(i);
            String nodeName = childChildNode.getNodeName();
            int index = 0;
            boolean bool = false;
            switch (nodeName) {
            case "role":
                role = nullNodeToString(childChildNode);
                break;
            case "resourceType":
                resourceType = nullNodeToString(childChildNode);
                break;
            case "folder":
                folder = nullNodeToString(childChildNode);
                break;
            case "folderView":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FOLDER_VIEW;
                break;
            case "folderCreate":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FOLDER_CREATE;
                break;
            case "folderRename":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FOLDER_RENAME;
                break;
            case "folderDelete":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FOLDER_DELETE;
                break;
            case "fileView":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FILE_VIEW;
                break;
            case "fileUpload":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FILE_UPLOAD;
                break;
            case "fileRename":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FILE_RENAME;
                break;
            case "fileDelete":
                bool = Boolean.parseBoolean(nullNodeToString(childChildNode));
                index = AccessControl.FILE_DELETE;
                break;
            }
            if (index != 0) {
                if (bool) {
                    mask |= index;
                } else {
                    mask &= ~index;
                }
            }
        }

        if (resourceType == null || role == null) {
            return null;
        }

        if (folder == null || folder.isEmpty()) {
            folder = "/";
        }
        return AccessControlLevel.builder().folder(folder).resourceType(resourceType).role(role).mask(mask).build();
    }

    /**
     * creates thumb properties from XML.
     *
     * @param childNodes list of thumb XML nodes
     * @param basePathBuilder base url and path builder
     * @param basePath base path
     * @throws ConnectorException when error occurs
     * @throws IOException when IO Exception occurs.
     */
    @SuppressWarnings("deprecation")
    private ThumbnailProperties createThumbs(NodeList childNodes, Path basePath, BasePathBuilder basePathBuilder)
            throws ConnectorException, IOException {
        boolean enabled = true;
        ThumbnailProperties.Builder thumbnail = ThumbnailProperties.builder();
        for (int i = 0, j = childNodes.getLength(); i < j; i++) {
            Node childNode = childNodes.item(i);
            switch (childNode.getNodeName()) {
            case "enabled":
                enabled = Boolean.parseBoolean(nullNodeToString(childNode));
                break;
            case "url":
                thumbnail.url(PathUtils.normalizeUrl(basePathBuilder.getBaseUrl()
                        + nullNodeToString(childNode).replace(Constants.BASE_URL_PLACEHOLDER, "")));
                break;
            case "directory":
                String thumbsDir = nullNodeToString(childNode).replace(Constants.BASE_DIR_PLACEHOLDER, "");
                Path file = getPath(basePath, thumbsDir);
                if (file == null) {
                    throw new ConnectorException(ErrorCode.FOLDER_NOT_FOUND,
                            "Thumbs directory could not be created using specified path.");
                }
                thumbnail.path(Files.createDirectories(file));
                break;
            case "directAccess":
                thumbnail.directAccess(Boolean.parseBoolean(nullNodeToString(childNode)));
                break;
            case "maxHeight":
                String width = nullNodeToString(childNode);
                width = width.replaceAll("\\D", "");
                try {
                    thumbnail.maxHeight(Integer.valueOf(width));
                } catch (NumberFormatException e) {
                    thumbnail.maxHeight(Constants.DEFAULT_THUMB_MAX_WIDTH);
                }
                break;
            case "maxWidth":
                width = nullNodeToString(childNode);
                width = width.replaceAll("\\D", "");
                try {
                    thumbnail.maxWidth(Integer.valueOf(width));
                } catch (NumberFormatException e) {
                    thumbnail.maxWidth(Constants.DEFAULT_THUMB_MAX_HEIGHT);
                }
                break;
            case "quality":
                String quality = nullNodeToString(childNode);
                quality = quality.replaceAll("\\D", "");
                thumbnail.quality(adjustQuality(quality));
            }
        }
        return enabled ? thumbnail.build() : null;
    }

    /**
     * Creates resource types configuration from XML configuration file (from XML
     * element 'types').
     *
     * @param builder context builder
     * @param doc XML document.
     * @param basePathBuilder base url and path builder
     * @throws IOException when IO Exception occurs.
     * @throws ConnectorException when error occurs
     */
    private void setTypes(DefaultCKFinderContext.Builder builder, Document doc, BasePathBuilder basePathBuilder,
            ThumbnailProperties thumbnail) throws IOException, ConnectorException {
        NodeList list = doc.getElementsByTagName("type");

        for (int i = 0, j = list.getLength(); i < j; i++) {
            Element element = (Element) list.item(i);
            String name = element.getAttribute("name");
            if (name != null && !name.isEmpty()) {
                ResourceType resourceType = createTypeFromXml(name, element.getChildNodes(), basePathBuilder,
                        thumbnail);
                builder.type(name, resourceType);
            }
        }
    }

    /**
     * Creates single resource type configuration from XML configuration file
     * (from XML element 'type').
     *
     * @param typeName name of type.
     * @param childNodes type XML child nodes.
     * @param basePathBuilder base url and path builder
     * @return parsed resource type
     * @throws IOException when IO Exception occurs.
     * @throws ConnectorException when error occurs
     */
    @SuppressWarnings("deprecation")
    private ResourceType createTypeFromXml(String typeName, NodeList childNodes, BasePathBuilder basePathBuilder,
            ThumbnailProperties thumbnail) throws IOException, ConnectorException {
        ResourceType.Builder builder = ResourceType.builder().name(typeName);
        String path = typeName.toLowerCase();
        String url = typeName.toLowerCase();

        for (int i = 0, j = childNodes.getLength(); i < j; i++) {
            Node childNode = childNodes.item(i);
            switch (childNode.getNodeName()) {
            case "url":
                url = nullNodeToString(childNode);
                break;
            case "directory":
                path = nullNodeToString(childNode);
                break;
            case "maxSize":
                long maxSize = 0;
                try {
                    parseMaxSize(nullNodeToString(childNode));
                } catch (NumberFormatException | IndexOutOfBoundsException ex) {
                }
                builder.maxSize(maxSize);
                break;
            case "allowedExtensions":
                builder.allowedExtensions(nullNodeToString(childNode));
                break;
            case "deniedExtensions":
                builder.deniedExtensions(nullNodeToString(childNode));
            }
        }
        url = basePathBuilder.getBaseUrl() + url.replace(Constants.BASE_URL_PLACEHOLDER, "");
        url = PathUtils.normalizeUrl(url);
        path = path.replace(Constants.BASE_DIR_PLACEHOLDER, "");

        Path p = getPath(basePathBuilder.getBasePath(), path);
        if (!p.isAbsolute()) {
            throw new ConnectorException(ErrorCode.FOLDER_NOT_FOUND,
                    "Resource directory could not be created using specified path.");
        }
        return builder.url(url).path(Files.createDirectories(p))
                .thumbnailPath(getPath(thumbnail != null ? thumbnail.getPath() : null, path)).build();
    }

    /**
     * parses max size value from config (ex. 16M to number of bytes).
     *
     * @param maxSize string representation of the max size
     * @return number of bytes in max size.
     */
    private long parseMaxSize(String maxSize) {
        char lastChar = Character.toLowerCase(maxSize.charAt(maxSize.length() - 1));
        int a = 1, off = 1;
        switch (lastChar) {
        case 'k':
            a = BYTES;
            break;
        case 'm':
            a = BYTES * BYTES;
            break;
        case 'g':
            a = BYTES * BYTES * BYTES;
            break;
        default:
            off = 0;
            break;
        }
        long value = Long.parseLong(maxSize.substring(0, maxSize.length() - off));
        return value * a;
    }

    /**
     * Sets plugins list from XML configuration file.
     *
     * @param builder context builder
     * @param childNode child of XML node 'plugins'.
     * @param resourceLoader resource loader to load xml configuration and
     * watermark resource
     */
    private void setPlugins(DefaultCKFinderContext.Builder builder, Node childNode, ResourceLoader resourceLoader) {
        NodeList nodeList = childNode.getChildNodes();
        int length = nodeList.getLength();
        List<Plugin> plugins = new ArrayList<>(length);
        for (int i = 0; i < length; i++) {
            Node childChildNode = nodeList.item(i);
            if ("plugin".equals(childChildNode.getNodeName())) {
                PluginInfo pluginInfo = createPluginFromNode(childChildNode);
                String name = pluginInfo.getName();
                if (name != null) {
                    Plugin plugin;
                    switch (name) {
                    case "imageresize":
                        try {
                            plugin = new ImageResizePlugin(pluginInfo.getParams().entrySet().stream()
                                    .collect(Collectors.toMap(entry -> ImageResizeParam.valueOf(entry.getKey()),
                                            entry -> new ImageResizeSize(entry.getValue()))));
                        } catch (IllegalArgumentException ex) {
                            plugin = new ImageResizePlugin(ImageResizeParam.createDefaultParams());
                        }
                        break;
                    case "watermark":
                        WatermarkSettings watermarkSettings = parseWatermarkSettings(pluginInfo, resourceLoader);
                        plugin = new WatermarkPlugin(watermarkSettings);
                        break;
                    case "fileeditor":
                        plugin = new FileEditorPlugin();
                        break;
                    default:
                        continue;
                    }
                    plugins.add(plugin);
                }
            }
        }
        builder.eventsFromPlugins(plugins);
    }

    private WatermarkSettings parseWatermarkSettings(PluginInfo pluginInfo, ResourceLoader resourceLoader) {
        WatermarkSettings.Builder settings = WatermarkSettings.builder();
        for (Map.Entry<String, String> entry : pluginInfo.getParams().entrySet()) {
            final String name = entry.getKey();
            final String value = entry.getValue();
            switch (name) {
            case "source":
                settings.source(resourceLoader.getResource(value));
                break;
            case "transparency":
                settings.transparency(Float.parseFloat(value));
                break;
            case "quality":
                final int parseInt = Integer.parseInt(value);
                final int name1 = parseInt % 101;
                final float name2 = name1 / 100f;
                settings.quality(name2);
                break;
            case "marginBottom":
                settings.marginBottom(Integer.parseInt(value));
                break;
            case "marginRight":
                settings.marginRight(Integer.parseInt(value));
                break;
            }
        }
        return settings.build();
    }

    /**
     * Creates plugin data from configuration file.
     *
     * @param element XML plugin node.
     * @return PluginInfo data
     */
    private PluginInfo createPluginFromNode(Node element) {
        PluginInfo.Builder builder = PluginInfo.builder();
        NodeList list = element.getChildNodes();
        for (int i = 0, l = list.getLength(); i < l; i++) {
            Node childElem = list.item(i);
            String nodeName = childElem.getNodeName();
            String textContent = nullNodeToString(childElem);
            switch (nodeName) {
            case "name":
                builder.name(textContent);
                break;
            case "params":
                NodeList paramLlist = childElem.getChildNodes();
                for (int j = 0, m = paramLlist.getLength(); j < m; j++) {
                    Node node = paramLlist.item(j);
                    if ("param".equals(node.getNodeName())) {
                        NamedNodeMap map = node.getAttributes();
                        String name = null;
                        String value = null;
                        for (int k = 0, o = map.getLength(); k < o; k++) {
                            Node item = map.item(k);
                            String nodeName1 = item.getNodeName();
                            if ("name".equals(nodeName1)) {
                                name = nullNodeToString(item);
                            } else if ("value".equals(nodeName1)) {
                                value = nullNodeToString(item);
                            }
                        }
                        builder.param(name, value);
                    }
                }
                break;
            }
        }
        return builder.build();
    }

    private Path getPath(Path first, String... more) {
        return first == null ? null : first.getFileSystem().getPath(first.toString(), more);
    }

}