pt.webdetails.cdf.dd.model.meta.reader.cdexml.fs.XmlFsPluginModelReader.java Source code

Java tutorial

Introduction

Here is the source code for pt.webdetails.cdf.dd.model.meta.reader.cdexml.fs.XmlFsPluginModelReader.java

Source

/*!
 * Copyright 2002 - 2015 Webdetails, a Pentaho company. All rights reserved.
 *
 * This software was developed by Webdetails and is provided under the terms
 * of the Mozilla Public License, Version 2.0, or any later version. You may not use
 * this file except in compliance with the license. If you need a copy of the license,
 * please go to http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails.
 *
 * Software distributed under the Mozilla Public License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
 * the license for the specific language governing your rights and limitations.
 */

package pt.webdetails.cdf.dd.model.meta.reader.cdexml.fs;

import org.apache.commons.io.FilenameUtils;
import pt.webdetails.cdf.dd.CdeEngine;
import pt.webdetails.cdf.dd.model.meta.reader.cdexml.XmlAdhocComponentTypeReader;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import pt.webdetails.cdf.dd.model.meta.ComponentType;
import pt.webdetails.cdf.dd.model.meta.MetaModel;
import pt.webdetails.cdf.dd.model.meta.PropertyType;
import pt.webdetails.cdf.dd.model.core.reader.ThingReadException;
import pt.webdetails.cdf.dd.model.meta.CustomComponentType;
import pt.webdetails.cdf.dd.model.meta.PrimitiveComponentType;
import pt.webdetails.cdf.dd.model.meta.WidgetComponentType;
import pt.webdetails.cpf.packager.origin.PluginRepositoryOrigin;
import pt.webdetails.cpf.packager.origin.RepositoryPathOrigin;
import pt.webdetails.cpf.packager.origin.StaticSystemOrigin;
import pt.webdetails.cdf.dd.util.CdeEnvironment;
import pt.webdetails.cdf.dd.util.GenericBasicFileFilter;
import pt.webdetails.cdf.dd.util.Utils;
import pt.webdetails.cpf.packager.origin.PathOrigin;
import pt.webdetails.cpf.repository.api.IBasicFile;
import pt.webdetails.cpf.repository.api.IContentAccessFactory;
import pt.webdetails.cpf.repository.api.IReadAccess;

/**
 * Loads XML model files, component types and property types, from the file system, of a Pentaho CDE plugin
 * instalation.
 *
 * @author dcleao
 */
public final class XmlFsPluginModelReader {

    public static final String RESOURCES_DIR = "resources";

    public static final String DEF_BASE_TYPE = PrimitiveComponentType.class.getSimpleName();

    public static final String BASE_DIR = Utils.joinPath(RESOURCES_DIR, "base");
    public static final String BASE_PROPS_DIR = Utils.joinPath(BASE_DIR, "properties");
    public static final String BASE_COMPS_DIR = Utils.joinPath(BASE_DIR, "components");

    public static final String DEF_CUSTOM_TYPE = CustomComponentType.class.getSimpleName();
    public static final String CUSTOM_DIR = Utils.joinPath(RESOURCES_DIR, "custom");
    public static final String CUSTOM_PROPS_DIR = Utils.joinPath(CUSTOM_DIR, "properties");

    public static final String DEF_WIDGET_STUB_TYPE = WidgetComponentType.class.getSimpleName();
    public static final String WIDGETS_DIR = "widgets";

    public static final String CUSTOM_PROPS_FILENAME = "property";
    public static final String COMPONENT_FILENAME = "component";
    // extension for component properties definitions
    public static final String DEFINITION_FILE_EXT = "xml";

    protected static final Log logger = LogFactory.getLog(XmlFsPluginModelReader.class);

    // -----------

    private final Boolean continueOnError;

    private IContentAccessFactory contentAccessFactory;

    public XmlFsPluginModelReader(boolean continueOnError) //the real ctor, only usage calls with false
    {
        this.continueOnError = continueOnError;
    }

    public XmlFsPluginModelReader(IContentAccessFactory caf, boolean continueOnError) {
        this(false);
        contentAccessFactory = caf;
    }

    /**
     * Reads properties, components and widgets
     *
     * @return model with component and property types
     * @throws ThingReadException
     */
    public MetaModel.Builder read(XmlFsPluginThingReaderFactory factory) throws ThingReadException {
        MetaModel.Builder model = new MetaModel.Builder();

        // Read Properties
        this.readBaseProperties(model, factory);
        this.readCustomProperties(model, factory);

        // Read Components
        logger.info(String.format("Loading BASE components from: %s", BASE_COMPS_DIR));
        this.readBaseComponents(model, factory);
        this.readCustomComponents(model, factory);
        this.readWidgetStubComponents(model, factory);

        return model;
    }

    private void readBaseComponents(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory)
            throws ThingReadException {
        List<IBasicFile> filesList = CdeEnvironment.getPluginSystemReader(BASE_COMPS_DIR).listFiles(null,
                new GenericBasicFileFilter(null, DEFINITION_FILE_EXT), IReadAccess.DEPTH_ALL);
        PathOrigin origin = new StaticSystemOrigin(BASE_COMPS_DIR);

        if (filesList != null) {
            IBasicFile[] filesArray = filesList.toArray(new IBasicFile[] {});
            Arrays.sort(filesArray, getFileComparator());
            for (IBasicFile file : filesArray) {
                this.readComponentsFile(model, factory, file, DEF_BASE_TYPE, origin);
            }
        }
    }

    private void readBaseProperties(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory)
            throws ThingReadException {

        logger.info(String.format("Loading BASE properties from: %s", BASE_PROPS_DIR));

        List<IBasicFile> filesList = CdeEnvironment.getPluginSystemReader(BASE_PROPS_DIR).listFiles(null,
                new GenericBasicFileFilter(null, DEFINITION_FILE_EXT), IReadAccess.DEPTH_ALL);

        if (filesList != null) {
            IBasicFile[] filesArray = filesList.toArray(new IBasicFile[] {});
            Arrays.sort(filesArray, getFileComparator());
            for (IBasicFile file : filesArray) {
                this.readPropertiesFile(model, factory, file);
            }
        }
    }

    private void readCustomProperties(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory)
            throws ThingReadException {

        logger.info(String.format("Loading CUSTOM properties from: %s", CUSTOM_PROPS_DIR));

        List<IBasicFile> filesList = CdeEnvironment.getPluginSystemReader(CUSTOM_PROPS_DIR).listFiles(null,
                new GenericBasicFileFilter(CUSTOM_PROPS_FILENAME, DEFINITION_FILE_EXT), IReadAccess.DEPTH_ALL);

        if (filesList != null) {
            IBasicFile[] filesArray = filesList.toArray(new IBasicFile[] {});
            Arrays.sort(filesArray, getFileComparator());
            for (IBasicFile file : filesArray) {
                this.readPropertiesFile(model, factory, file);
            }
        }
    }

    private void readPropertiesFile(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory, IBasicFile file)
            throws ThingReadException {
        Document doc;
        try {
            doc = Utils.getDocFromFile(file, null);
        } catch (Exception ex) {
            ThingReadException ex2 = new ThingReadException("Cannot read properties file '" + file + "'.", ex);
            if (!this.continueOnError) {
                throw ex2;
            }

            // log and move on
            logger.fatal(null, ex2);
            return;
        }

        // One file can contain multiple definitions.
        @SuppressWarnings("unchecked")
        List<Element> propertieElems = doc.selectNodes("//DesignerProperty");
        for (Element propertyElem : propertieElems) {
            readProperty(model, factory, propertyElem, file);
        }
    }

    private void readProperty(MetaModel.Builder modelBuilder, XmlFsPluginThingReaderFactory factory,
            Element propertyElem, IBasicFile file) {
        try {
            PropertyType.Builder prop = factory.getPropertyTypeReader().read(propertyElem, file.getPath());
            modelBuilder.addProperty(prop);
        } catch (IllegalArgumentException ex) {
            if (!this.continueOnError) {
                throw ex;
            }

            // Just log and move on
            logger.fatal("Failed to read property ", ex);
        }
    }

    private void readCustomComponents(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory)
            throws ThingReadException {
        for (PathOrigin origin : CdeEnvironment.getPluginResourceLocationManager().getCustomComponentsLocations()) {
            readCustomComponentsLocation(model, factory, origin);
        }
    }

    private void readCustomComponentsLocation(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory,
            PathOrigin origin) throws ThingReadException {
        logger.info("reading custom components from " + origin);

        GenericBasicFileFilter filter = new GenericBasicFileFilter(COMPONENT_FILENAME, DEFINITION_FILE_EXT);
        IReadAccess access = origin.getReader(contentAccessFactory);
        List<IBasicFile> filesList = access.listFiles(null, filter, IReadAccess.DEPTH_ALL, false, true);

        if (filesList != null) {
            logger.debug(String.format("%d sub-folders found", filesList.size()));
            IBasicFile[] filesArray = filesList.toArray(new IBasicFile[] {});
            Arrays.sort(filesArray, getFileComparator());
            for (IBasicFile file : filesArray) {
                this.readComponentsFile(model, factory, file, DEF_CUSTOM_TYPE, origin);
            }
        }
    }

    private void readWidgetStubComponents(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory)
            throws ThingReadException {
        Document doc = null;
        IBasicFile cdeXml = CdeEngine.getInstance().getEnvironment().getCdeXml();
        try {
            if (cdeXml != null) {
                doc = Utils.getDocFromFile(cdeXml, null);
            }
        } catch (Exception e) {
            String msg = "Cannot read components file 'cde.xml'.";

            if (!this.continueOnError) {
                throw new ThingReadException(msg, e);
            }
            // log and move on
            logger.fatal(msg, e);
            return;
        }
        if (doc != null) {
            List<Element> widgetLocations = doc.selectNodes("//widgetsLocations//location");
            List<List<IBasicFile>> widgetsLists = new ArrayList<List<IBasicFile>>();
            String locations = "";
            for (Element location : widgetLocations) {
                try {
                    List<IBasicFile> filesList;
                    String path = location.getText().toLowerCase().replaceFirst("/", "");

                    if (path.startsWith(CdeEnvironment.getSystemDir() + "/")) {
                        filesList = CdeEnvironment.getPluginSystemReader().listFiles(location.getText(),
                                new GenericBasicFileFilter(COMPONENT_FILENAME, DEFINITION_FILE_EXT),
                                IReadAccess.DEPTH_ALL, false, true);
                    } else {
                        filesList = CdeEnvironment.getUserContentAccess().listFiles(location.getText(),
                                new GenericBasicFileFilter(COMPONENT_FILENAME, DEFINITION_FILE_EXT),
                                IReadAccess.DEPTH_ALL, false, true);
                    }

                    if (filesList != null) {
                        widgetsLists.add(filesList);
                    }
                    locations = locations + location.getText() + ", ";
                } catch (Exception e) {
                    logger.fatal("Couldn't load widgets from: " + location.getText(), e);
                }
            }
            logger.info(String.format("Loading WIDGET components from: %s", locations));
            List<String> files = new ArrayList<String>();
            if (widgetsLists.size() > 0) {
                for (List<IBasicFile> filesList : widgetsLists) {
                    for (IBasicFile file : filesList) {
                        if (!files.contains(file.getName())) {
                            files.add(file.getName());
                            fixWidgetMeta(file);
                            this.readComponentsFile(model, factory, file, DEF_WIDGET_STUB_TYPE,
                                    new RepositoryPathOrigin(FilenameUtils.getPath(file.getPath())));
                        } else {
                            logger.debug("Duplicate widget, ignoring " + file.getPath());
                        }
                    }
                }
            }
            return;
        }

        logger.info(String.format("Loading WIDGET components from: %s", WIDGETS_DIR));

        List<IBasicFile> filesList = CdeEnvironment.getPluginRepositoryReader(WIDGETS_DIR).listFiles(null,
                new GenericBasicFileFilter(COMPONENT_FILENAME, DEFINITION_FILE_EXT), IReadAccess.DEPTH_ALL, false,
                true);
        PathOrigin widgetsOrigin = new PluginRepositoryOrigin(CdeEngine.getEnv().getPluginRepositoryDir(),
                WIDGETS_DIR);

        if (filesList != null) {
            logger.debug(String.format("%s widget components found", filesList.size()));
            IBasicFile[] filesArray = filesList.toArray(new IBasicFile[] {});
            Arrays.sort(filesArray, getFileComparator());
            for (IBasicFile file : filesArray) {
                this.readComponentsFile(model, factory, file, DEF_WIDGET_STUB_TYPE, widgetsOrigin);
            }
        }

    }

    private void fixWidgetMeta(IBasicFile componentXml) {
        Document doc = null;

        try {
            if (CdeEnvironment.getUserContentAccess().fileExists(componentXml.getPath())) {
                doc = Utils.getDocFromFile(CdeEnvironment.getUserContentAccess().fetchFile(componentXml.getPath()),
                        null);
            } else if (CdeEnvironment.getPluginSystemReader().fileExists(componentXml.getPath())) {
                doc = Utils.getDocFromFile(CdeEnvironment.getPluginSystemReader().fetchFile(componentXml.getPath()),
                        null);
            }
        } catch (Exception e) {
            logger.error("Unable to check meta for " + componentXml.getPath() + ", moving on");
            return;
        }
        List<Element> wcdfMeta = doc.selectNodes("//meta[@name='wcdf']");
        String wcdfName = componentXml.getName().replace(".component.xml", ".wcdf");
        String wcdfPath = FilenameUtils.getPath(componentXml.getPath()) + wcdfName;

        for (Element meta : wcdfMeta) {
            String metaText = meta.getText();

            if (CdeEnvironment.getPluginSystemWriter().fileExists(metaText)) {
                wcdfPath = metaText;
            }

            if (metaText.startsWith("/") && !wcdfPath.startsWith("/")) {
                wcdfPath = "/" + wcdfPath;
            }

            if (metaText.equals(wcdfPath)) {
                logger.debug("No need to fix current wcdf meta ( " + metaText + " ) ");
            } else {
                logger.debug("Fixing wcdf meta, was " + metaText + ", setting " + wcdfPath);
                meta.setText(wcdfPath);
                try {
                    if (CdeEnvironment.getUserContentAccess().fileExists(componentXml.getPath())) {
                        CdeEnvironment.getUserContentAccess().saveFile(componentXml.getPath(),
                                new ByteArrayInputStream(doc.asXML().getBytes()));
                    } else if (CdeEnvironment.getPluginSystemWriter().fileExists(componentXml.getPath())) {
                        CdeEnvironment.getPluginSystemWriter().saveFile(componentXml.getPath(),
                                new ByteArrayInputStream(doc.asXML().getBytes()));
                    }
                } catch (Exception e) {
                    logger.error("Unable to fix meta for " + componentXml.getName() + ", moving on");
                }
            }
        }
    }

    private Comparator<IBasicFile> getFileComparator() {
        return new Comparator<IBasicFile>() {
            @Override
            public int compare(IBasicFile file1, IBasicFile file2) {
                return file1.getFullPath().toLowerCase().compareTo(file2.getFullPath().toLowerCase());
            }
        };
    }

    private void readComponentsFile(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory, IBasicFile file,
            String defaultClassName, PathOrigin origin) throws ThingReadException {
        Document doc;
        try {
            doc = Utils.getDocFromFile(file, null);
        } catch (Exception ex) {
            String msg = "Cannot read components file '" + file.getFullPath() + "'.";

            if (!this.continueOnError) {
                throw new ThingReadException(msg, ex);
            }
            // log and move on
            logger.fatal(msg, ex);
            return;
        }

        // One file can contain multiple definitions.
        @SuppressWarnings("unchecked")
        List<Element> componentElems = doc.selectNodes("//DesignerComponent");

        if (logger.isDebugEnabled() && componentElems.size() > 0) {
            logger.debug(String.format("\t%s [%s]", file.getPath(), componentElems.size()));
        }

        for (Element componentElem : componentElems) {
            readComponent(model, factory, componentElem, file.getPath(), defaultClassName, origin);
        }
    }

    private void readComponent(MetaModel.Builder model, XmlFsPluginThingReaderFactory factory,
            Element componentElem, String sourcePath, String defaultClassName, PathOrigin origin)
            throws ThingReadException {
        String className = Utils.getNodeText("Header/Override", componentElem);

        if (StringUtils.isEmpty(className)) {
            className = defaultClassName;
        }

        String name = Utils.getNodeText("Header/Name", componentElem);

        XmlAdhocComponentTypeReader<? extends ComponentType.Builder> reader = factory
                .getComponentTypeReader(className);
        if (reader == null) {
            String msg = "Failed to read component of class '" + className + "' and name " + name;
            if (!this.continueOnError) {
                throw new ThingReadException(msg);
            }
            // Just log and move on
            logger.fatal(msg);
        }

        ComponentType.Builder comp = reader.read(componentElem, origin, sourcePath);
        comp.setOrigin(origin);
        model.addComponent(comp);
    }

}