All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.oneandone.lavender.modules.DefaultModule Maven / Gradle / Ivy

/*
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * 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 net.oneandone.lavender.modules;

import net.oneandone.lavender.config.Docroot;
import net.oneandone.sushi.fs.Node;
import net.oneandone.sushi.fs.World;
import net.oneandone.sushi.fs.file.FileNode;
import net.oneandone.sushi.fs.filter.Action;
import net.oneandone.sushi.fs.filter.Filter;
import net.oneandone.sushi.fs.filter.Predicate;
import net.oneandone.sushi.xml.Selector;
import net.oneandone.sushi.xml.XmlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public abstract class DefaultModule extends Module {
    private static final Logger LOG = LoggerFactory.getLogger(Module.class);
    // used to detect a recent parent pom
    private static final String RESOURCE_INDEX = "META-INF/pustefix-resource.index";


    public static List fromWebapp(FileNode cache, boolean prod, Node webapp, String svnUsername, String svnPassword)
            throws IOException, SAXException, XmlException {
        Node webappSource;
        List result;
        WarConfig rootConfig;
        DefaultModule root;
        LavenderProperties lp;

        LOG.trace("scanning " + webapp);
        lp = LavenderProperties.loadApp(prod, webapp);
        result = new ArrayList<>();
        rootConfig = WarConfig.fromXml(webapp);
        // add modules before webapp, because they have a prefix
        for (Node jar : webapp.find("WEB-INF/lib/*.jar")) {
            result.addAll(jarModuleOpt(cache, rootConfig, prod, jar, svnUsername, svnPassword));
        }
        webappSource = lp.live(webapp);
        root = warModule(rootConfig, lp.filter, webappSource);
        result.add(root);
        lp.addModules(cache, prod, svnUsername, svnPassword, result, null);
        return result;
    }

    public static List jarModuleOpt(FileNode cache, WarConfig rootConfig, boolean prod, Node jarOrig,
                                            String svnUsername, String svnPassword) throws IOException, XmlException, SAXException {
        Node configFile;
        final JarConfig config;
        List result;
        Node jarTmp;
        final Node jarLive;
        Module jarModule;
        Object[] tmp;
        LavenderProperties lp;
        Node exploded;
        final Filter filter;
        boolean hasResourceIndex;

        result = new ArrayList<>();
        if (jarOrig instanceof FileNode) {
            // TODO: expensive
            exploded = ((FileNode) jarOrig).openJar();
            configFile = exploded.join("META-INF/pustefix-module.xml");
            if (!configFile.exists()) {
                return result;
            }
            try (InputStream src = configFile.newInputStream()) {
                config = JarConfig.load(jarOrig.getWorld().getXml(), rootConfig, src);
            }
            lp = LavenderProperties.loadModuleOpt(prod, exploded);
            if (!prod && lp == null) {
                // This module has no lavender.properties, and thus pominfo.properties is outdated and thus lavender's live mechanism won't work.
                // So we ignore this module in devel mode to get requests passed through to pustefix, which can do his old live handling
                // TODO: report "missing lavender.properties" error when all modules have been updated ...
                return result;
            }
            hasResourceIndex = exploded.join(RESOURCE_INDEX).exists();
            if (prod || lp == null) {
                jarTmp = jarOrig;
            } else {
                jarTmp = lp.live(jarOrig);
            }
            if (jarTmp.isFile()) {
                jarLive = ((FileNode) jarTmp).openJar();
            } else {
                jarLive = jarTmp;
            }
            filter = lp == null ? LavenderProperties.defaultFilter() : lp.filter;
            jarModule = new DefaultModule(Docroot.WEB, config.getModuleName(), true, config.getResourcePathPrefix(), "", filter) {
                @Override
                protected Map scan(Filter filter) throws IOException {
                    return files(filter, config, jarLive);
                }
            };
        } else {
            if (!prod) {
                throw new UnsupportedOperationException("live mechanism not supported for jar streams");
            }
            tmp = DefaultModule.fromJarStream(prod, Docroot.WEB, rootConfig, jarOrig);
            if (tmp == null) {
                // no pustefix module config
                return result;
            }
            jarModule = (Module) tmp[0];
            lp = (LavenderProperties) tmp[1];
            config = (JarConfig) tmp[2];
            hasResourceIndex = (Boolean) tmp[3];
        }
        if (lp == null && hasResourceIndex) {
            // ok - we have a recent parent pom without lavender properties
            // -> the has not enabled lavender for this module
            return result;
        }
        // continue without lavender.properties -- we have to support this mode for a some time ... :(
        result.add(jarModule);
        if (lp != null) {
            lp.addModules(cache, prod, svnUsername, svnPassword, result, config);
        }
        return result;
    }

    private static Map files(final Filter filter, final JarConfig config, final Node exploded) throws IOException {
        Filter f;
        final Map result;

        result = new HashMap<>();
        f = exploded.getWorld().filter().predicate(Predicate.FILE).includeAll();
        f.invoke(exploded, new Action() {
            public void enter(Node node, boolean isLink) {
            }

            public void enterFailed(Node node, boolean isLink, IOException e) throws IOException {
                throw e;
            }

            public void leave(Node node, boolean isLink) {
            }

            public void select(Node node, boolean isLink) {
                String path;
                String resourcePath;

                path = node.getRelative(exploded);
                if (filter.matches(path)) {
                    resourcePath = config.getPath(path);
                    if (resourcePath != null) {
                        result.put(resourcePath, node);
                    }
                }
            }
        });
        return result;
    }

    //--

    public static DefaultModule warModule(final WarConfig config, final Filter filter, final Node webapp) throws IOException {
        Element root;
        Selector selector;
        String name;

        try {
            root = webapp.join("WEB-INF/project.xml").readXml().getDocumentElement();
            selector = webapp.getWorld().getXml().getSelector();
            name = selector.string(root, "project/name");
            return new DefaultModule(Docroot.WEB, name, true, "", "", filter) {
                @Override
                protected Map scan(Filter filter) throws IOException {
                    return scanExploded(config, filter, webapp);
                }
            };
        } catch (SAXException | XmlException e) {
            throw new IOException("cannot load project descriptor: " + e);
        }
    }

    private static Map scanExploded(final WarConfig global, final Filter filter, final Node exploded) throws IOException {
        Filter f;
        final Map result;

        result = new HashMap<>();
        f = exploded.getWorld().filter().predicate(Predicate.FILE).includeAll();
        f.invoke(exploded, new Action() {
            public void enter(Node node, boolean isLink) {
            }

            public void enterFailed(Node node, boolean isLink, IOException e) throws IOException {
                throw e;
            }

            public void leave(Node node, boolean isLink) {
            }

            public void select(Node node, boolean isLink) {
                String path;

                path = node.getRelative(exploded);
                if (filter.matches(path) && global.isPublicResource(path)) {
                    result.put(path, node);
                }
            }
        });
        return result;
    }

    //--

    /** To properly make jars available as a module, I have to load them into memory when the jar is itself contained in a war. */
    public static Object[] fromJarStream(boolean prod, String type, WarConfig parent, Node jar) throws IOException {
        JarConfig config;
        Node[] loaded;
        Filter filter;
        World world;
        ZipEntry entry;
        String path;
        ZipInputStream src;
        Node root;
        Node child;
        Node propertyNode;
        final Map files;
        String resourcePath;
        LavenderProperties lp;

        loaded = LavenderProperties.loadStreamNodes(jar, "META-INF/pustefix-module.xml",
                LavenderProperties.MODULE_PROPERTIES, "META-INF/pominfo.properties", RESOURCE_INDEX);
        if (loaded[0] == null) {
            return null;
        }
        try (InputStream configSrc = loaded[0].newInputStream()) {
            config = JarConfig.load(jar.getWorld().getXml(), parent, configSrc);
        } catch (SAXException | XmlException e) {
            throw new IOException(jar + ": cannot load module descriptor:" + e.getMessage(), e);
        }
        propertyNode = loaded[1];
        if (propertyNode == null) {
            filter = LavenderProperties.defaultFilter();
            lp = null;
        } else {
            lp = LavenderProperties.loadNode(prod, propertyNode, loaded[2]);
            filter = lp.filter;
        }
        world = jar.getWorld();
        root = world.getMemoryFilesystem().root().node(UUID.randomUUID().toString(), null).mkdir();
        src = new ZipInputStream(jar.newInputStream());
        files = new HashMap<>();
        while ((entry = src.getNextEntry()) != null) {
            path = entry.getName();
            if (!entry.isDirectory()) {
                if ((resourcePath = config.getPath(path)) != null && filter.matches(path)) {
                    child = root.join(path);
                    child.getParent().mkdirsOpt();
                    world.getBuffer().copy(src, child);
                    files.put(resourcePath, child);
                }
            }
        }
        return new Object[] { new DefaultModule(type, config.getModuleName(), true, config.getResourcePathPrefix(), "", filter) {
            public Map scan(Filter filter) {
                // no need to re-scan files from memory
                return files;
            }
        }, lp, config, loaded[3] != null};
    }

    //--

    public DefaultModule(String type, String name, boolean lavendelize, String resourcePathPrefix, String targetPathPrefix, Filter filter) throws IOException {
        super(type, name, lavendelize, resourcePathPrefix, targetPathPrefix, filter);
    }

    protected Resource createResource(String resourcePath, Node file) throws IOException {
        return DefaultResource.forNode(file, resourcePath);
    }

    @Override
    public void saveCaches() {
        // nothing to do
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy