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

org.pustefixframework.live.LiveJarInfo Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of Pustefix.
 *
 * Pustefix is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Pustefix is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Pustefix; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.pustefixframework.live;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Provides information about Pustefix live resources. Also handles read and write operations of live.xml.
 */
public class LiveJarInfo {

    private static Logger LOG = LoggerFactory.getLogger(LiveJarInfo.class);
    
    public static final String[] DEFAULT_DOCROOT_LIVE_EXCLUSIONS = { 
            File.separator + "WEB-INF" + File.separator + "web.xml",
            File.separator + ".cache", 
            File.separator + "wsscript",
            File.separator + "wsdl", 
            File.separator + ".editorbackup", 
            File.separator + "htdocs" + File.separator + "sitemap.xml"};

    public static String PROP_LIVEROOT = "pustefix.liveroot";
    public static String PROP_LIVEROOT_MAXDEPTH = "pustefix.liveroot.maxdepth";
    public static String PROP_LIVE_FORCE_VERSION = "pustefix.live.forceversion";
    
    private static int DEFAULT_LIVEROOT_MAXDEPTH = 4;
    
    /** The live.xml file */
    private File file;

    private File liveRootDir;
    private int liveRootMaxDepth = DEFAULT_LIVEROOT_MAXDEPTH;
    
    private long lastReadTimestamp;
    
    private boolean forceVersion = true;

    /** The jar entries */
    private Map jarEntries;
    private Map jarEntriesNoVersion;

    /** The war entry */
    private Map warEntries;
    private Map warEntriesNoVersion;

    private Map rootToLocation;
    private Set rootsWithNoLocation;

    /**
     * Creates a new instance of LiveJarInfo, tries to detect the live.xml and parses that live.xml.
     */
    public LiveJarInfo() {

        String forceVersionProp = System.getProperty(PROP_LIVE_FORCE_VERSION);
        if(forceVersionProp != null) {
            forceVersion = Boolean.parseBoolean(forceVersionProp);
        }
        
        String liveRoot = System.getProperty(PROP_LIVEROOT);
        if(liveRoot != null) {
            
            liveRootDir = new File(liveRoot);
            try {
                liveRootDir = liveRootDir.getCanonicalFile();
            } catch (IOException e) {
                throw new RuntimeException("Can't read liveroot dir '" + liveRootDir.getPath() + "'.");
            }
            if(!liveRootDir.exists()) throw new RuntimeException("Liveroot dir '" + liveRootDir.getPath() + "' doesn't exist.");
            
            String value = System.getProperty(PROP_LIVEROOT_MAXDEPTH);
            if(value != null) {
                liveRootMaxDepth = Integer.parseInt(value);
            }
            
            LOG.info("Using maven projects found in '" + liveRootDir.getPath() + "' to look up live resources."); 
            
        } else {
        
            // search live.xml in workspace
            URL classpathUrl = getClass().getResource("/");
            if (classpathUrl != null) {
                String classpathDir = classpathUrl.getFile();
                if (classpathDir != null && classpathDir.length() > 0) {
                    File dir = new File(classpathDir);
                    do {
                        File test = new File(dir, "live.xml");
                        if (test.exists()) {
                            file = test;
                            LOG.info("Detected live.xml: " + file);
                            break;
                        }
                        dir = dir.getParentFile();
                    } while (dir != null);
                }
            }
    
            // fallback: use live.xml/life.xml from old location in ~/.m2
            if (file == null) {
                String homeDir = System.getProperty("user.home");
                File oldFile = new File(homeDir + File.separator + ".m2" + File.separator + "live.xml");
                if (!oldFile.exists()) {
                    oldFile = new File(homeDir + File.separator + ".m2" + File.separator + "life.xml"); // support old misspelled name
                }
                if (oldFile.exists()) {
                    file = oldFile;
                    LOG.warn("Using live.xml from old location: " + file);
                }
            }
            if (file == null) {
                LOG.info("No live.xml detected, default settings for live resources may be used!");
            }
    
        }
        
        init();
    }

    public LiveJarInfo(File file) {
        this.file = file;
        init();
    }

    private void init() {
        jarEntries = new HashMap();
        jarEntriesNoVersion = new HashMap();
        warEntries = new HashMap();
        warEntriesNoVersion = new HashMap();
        rootToLocation = new HashMap();
        rootsWithNoLocation = new HashSet();

        if (file != null) {
            try {
                read();
            } catch (Exception x) {
                throw new RuntimeException(x);
            }
        } else if(liveRootDir != null) {
            long t1 = System.currentTimeMillis();
            findMavenProjects(liveRootDir, 0, liveRootMaxDepth);
            long t2 = System.currentTimeMillis();
            LOG.info("Finding Maven projects took " + (t2-t1) + "ms.");
        }
        
        LOG.info(toString());
    }

    private void read() throws Exception {
        if (file.exists()) {
            lastReadTimestamp = file.lastModified();
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(file);
            Element root = document.getDocumentElement();
            if (root.getLocalName().equals("live") || root.getLocalName().equals("life")) { // support old misspelled
                // name
                for (Element jarElem : LiveUtils.getChildElements(root, "jar")) {
                    Entry entry = readEntry(jarElem);
                    jarEntries.put(entry.getId(), entry);
                    jarEntriesNoVersion.put(entry.getGroupId() + "+" + entry.getArtifactId(), entry);
                }
                for (Element warElem : LiveUtils.getChildElements(root, "war")) {
                    Entry entry = readEntry(warElem);
                    warEntries.put(entry.getId(), entry);
                    warEntriesNoVersion.put(entry.getGroupId() + "+" + entry.getArtifactId(), entry);
                }
            }
        } else {
            LOG.info("Live jar configuration " + file + " not found.");
        }
    }

    private Entry readEntry(Element jarElem) throws Exception {
        Entry entry = new Entry();
        Element idElem = LiveUtils.getSingleChildElement(jarElem, "id", true);
        Element groupElem = LiveUtils.getSingleChildElement(idElem, "group", true);
        entry.groupId = groupElem.getTextContent().trim();
        Element artifactElem = LiveUtils.getSingleChildElement(idElem, "artifact", true);
        entry.artifactId = artifactElem.getTextContent().trim();
        Element versionElem = LiveUtils.getSingleChildElement(idElem, "version", true);
        entry.version = versionElem.getTextContent().trim();
        List dirElems = LiveUtils.getChildElements(jarElem, "directory");
        if (dirElems.size() == 0)
            dirElems = LiveUtils.getChildElements(jarElem, "directorie"); // support old misspelled name
        for (Element dirElem : dirElems) {
            File dir = new File(dirElem.getTextContent().trim());
            entry.directories.add(dir);
        }
        return entry;
    }

    public void checkFileModified() {
        if (file != null && file.exists()) {
            if (file.lastModified() > lastReadTimestamp) {
                init();
            }
        }
    }

    /**
     * Writes the live information to the live.xml file.
     * @throws Exception
     *             the exception
     */
    public void write() throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.newDocument();

        Element live = document.createElement("live");
        document.appendChild(live);

        for (Entry entry : jarEntries.values()) {
            Node jar = live.appendChild(document.createElement("jar"));
            writeEntry(document, jar, entry);
        }
        for (Entry entry : warEntries.values()) {
            Node war = live.appendChild(document.createElement("war"));
            writeEntry(document, war, entry);
        }

        // Write the DOM document to the file
        Source source = new DOMSource(document);
        Result result = new StreamResult(file);
        Transformer serializer = TransformerFactory.newInstance().newTransformer();
        serializer.setOutputProperty(OutputKeys.INDENT, "yes");
        serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        serializer.transform(source, result);
    }

    private void writeEntry(Document document, Node jar, Entry entry) {
        Node id = jar.appendChild(document.createElement("id"));
        Node group = id.appendChild(document.createElement("group"));
        group.setTextContent(entry.groupId);
        Node artifact = id.appendChild(document.createElement("artifact"));
        artifact.setTextContent(entry.artifactId);
        Node version = id.appendChild(document.createElement("version"));
        version.setTextContent(entry.version);
        for (File directory : entry.directories) {
            Node directoryNode = jar.appendChild(document.createElement("directory"));
            directoryNode.setTextContent(directory.getAbsolutePath());
        }
    }

    public File getLiveFile() {
        return file;
    }

    public Map getJarEntries() {
        checkFileModified();
        return jarEntries;
    }

    public boolean hasJarEntries() {
        checkFileModified();
        return jarEntries != null && jarEntries.size() > 0;
    }

    public Map getWarEntries() {
        checkFileModified();
        return warEntries;
    }

    public boolean hasWarEntries() {
        checkFileModified();
        return warEntries != null && warEntries.size() > 0;
    }
    
    public static boolean isDefaultDocrootLiveExclusion(String path) {
        for (String s : DEFAULT_DOCROOT_LIVE_EXCLUSIONS) {
            if(path.startsWith(s) && 
                    (path.equals(s) || path.substring(s.length()).startsWith(File.separator))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the live docroot.
     * @param docroot
     *            the original docroot
     * @param path
     *            the path of the resource, relative to docroot, used to determine includes/excludes
     * @return the live location for the docroot resource, or null if no live location is available
     * @throws Exception
     */
    public File getLiveDocroot(String docroot, String path) throws Exception {
        checkFileModified();

        // TODO: excludes and includes, depending on path, per directory
        if(isDefaultDocrootLiveExclusion(path)) return null;

        File location = rootToLocation.get(docroot);
        if (location != null || rootsWithNoLocation.contains(docroot)) {
            return location;
        }

        // find pom.xml, retrieve groupId, artifactId, version from pom.xml
        File pomFile = LiveUtils.guessPom(docroot);
        if (pomFile != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Found pom.xml: " + pomFile);
            }
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(pomFile);
            Element root = document.getDocumentElement();
            Element groupElem = LiveUtils.getSingleChildElement(root, "groupId", true);
            String groupId = groupElem.getTextContent().trim();
            Element artifactElem = LiveUtils.getSingleChildElement(root, "artifactId", true);
            String artifactId = artifactElem.getTextContent().trim();
            Element versionElem = LiveUtils.getSingleChildElement(root, "version", true);
            String version = versionElem.getTextContent().trim();
            String entryKey = groupId + "+" + artifactId + "+" + version;

            Entry warEntry = warEntries.get(entryKey);
            if (warEntry == null && !forceVersion) warEntry = warEntriesNoVersion.get(groupId + "+" + artifactId);
            if (warEntry != null) {
                for (File dir : warEntry.directories) {
                    // if (dir.getName().equals("webapp")) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Found live docroot location by pom.xml: " + entryKey);
                    }
                    rootToLocation.put(docroot.toString(), dir);
                    return dir;
                    // }
                }   
            }
        }

        rootsWithNoLocation.add(docroot);
        return null;
    }

    /**
     * Gets the live module root.
     * @param url
     *            the URL to the module JAR or file target URL
     * @param path
     *            the resource path, relative to the URL
     * @return the live location for the module resource, or null if no live location is available
     */
    public File getLiveModuleRoot(URL url, String path) {
        checkFileModified();

        // TODO: excludes and includes, depending on path, per directory

        File location = rootToLocation.get(url.toString());
        if (location != null || rootsWithNoLocation.contains(url.toString())) {
            return location;
        }

        if (url.getProtocol().equals("jar")) {
            String jarFileName = url.getPath();
            int ind = jarFileName.indexOf('!');
            jarFileName = jarFileName.substring(0, ind);
            ind = jarFileName.lastIndexOf('/');
            jarFileName = jarFileName.substring(ind + 1);
            ind = jarFileName.lastIndexOf('.');
            jarFileName = jarFileName.substring(0, ind);
            // look for entry by jar file name
            Entry entry = jarEntries.get(jarFileName);
            if (entry != null) {
                for (File dir : entry.directories) {
                    if (dir.getName().equals("resources")) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Found live module location by jar file name: " + jarFileName);
                        }
                        rootToLocation.put(url.toString(), dir);
                        return dir;
                    }
                }
            }
            // look for entry by artifact name and MANIFEST attributes
            try {
                URL manifestUrl = new URL(url, "/META-INF/MANIFEST.MF");
                URLConnection con = manifestUrl.openConnection();
                if (con != null) {
                    InputStream in = con.getInputStream();
                    if (in != null) {
                        Manifest manifest = new Manifest(in);
                        Attributes attrs = manifest.getMainAttributes();
                        String groupId = attrs.getValue("Implementation-Vendor-Id");
                        String version = attrs.getValue("Implementation-Version");
                        if (groupId != null && version != null) {
                            int endInd = jarFileName.indexOf(version);
                            if(endInd == -1 && version.endsWith("-SNAPSHOT")) {
                                String versionStart = version.substring(0, version.length() - 8);
                                endInd = jarFileName.indexOf(versionStart);
                            }
                            if (endInd > 2) {
                                String artifactId = jarFileName.substring(0, endInd - 1);
                                String entryKey = groupId + "+" + artifactId + "+" + version;
                                entry = jarEntries.get(entryKey);
                                if(entry == null && !forceVersion) entry = jarEntriesNoVersion.get(groupId + "+" + artifactId);
                                if (entry != null) {
                                    for (File dir : entry.directories) {
                                        if (LOG.isDebugEnabled()) {
                                            LOG.debug("Found live module location by artifact name and MANIFEST attributes: "
                                                    + entryKey);
                                        }
                                        rootToLocation.put(url.toString(), dir);
                                        return dir;
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (FileNotFoundException x) {
                LOG.warn("Module contains no MANIFEST.MF: " + url.toString());
            } catch (MalformedURLException x) {
                LOG.warn("Illegal module URL: " + url.toString(), x);
            } catch (IOException x) {
                LOG.warn("IO error reading module data: " + url.toString(), x);
            }
        } else if (url.getProtocol().equals("file")) {
            // find pom.xml, retrieve groupId, artifactId, version from pom.xml
            try {
                File pomFile = LiveUtils.guessPom(url.getFile());
                if (pomFile != null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Found pom.xml: " + pomFile);
                    }
                    String entryKey = LiveUtils.getKeyFromPom(pomFile);
                    Entry jarEntry = jarEntries.get(entryKey);
                    if(jarEntry == null && !forceVersion) jarEntry = jarEntriesNoVersion.get(entryKey.substring(0, entryKey.lastIndexOf('+')));
                    if (jarEntry != null) {
                        for (File dir : jarEntry.directories) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Found live module location by pom.xml: " + entryKey);
                            }
                            rootToLocation.put(url.toString(), dir);
                            return dir;
                        }
                    }
                }
            } catch (Exception e) {
                LOG.warn("Exceptions reading module live POM: " + url.toString(), e);
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Found no live location: " + url.toString());
        }
        rootsWithNoLocation.add(url.toString());
        return null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Live jar information - ");
        int noJar = (jarEntries == null ? 0 : jarEntries.size());
        sb.append("Detected " + noJar + " live jar entr" + (noJar == 1 ? "y" : "ies"));
        int noWar = (warEntries == null ? 0 : warEntries.size());
        sb.append(" and " + noWar + " live war entr" + (noJar == 1 ? "y" : "ies"));
        return sb.toString();
    }
    
    private void findMavenProjects(File dir, int level, int maxDepth) {
        File[] files = dir.listFiles();
        for(File file: files) {
            if(file.isDirectory() && !file.isHidden() && !file.getName().equals("CVS") 
                    && !file.getName().equals("target") && !file.getName().equals("src")) {
                if(level < maxDepth) findMavenProjects(file, level + 1, maxDepth);
            } else if(file.isFile() && file.getName().equals("pom.xml") && file.canRead()) {
                Entry entry = null;
                try {
                    entry = LiveUtils.getEntryFromPom(file);
                } catch (Exception e) {
                    LOG.warn("Error reading POM: " + file.getAbsolutePath(), e);
                }
                if(entry != null) {
                    File resDir = new File(file.getParentFile(), "src/main/webapp");
                    if(resDir.exists()) {
                        List resDirs = new ArrayList();
                        resDirs.add(resDir);
                        entry.setDirectories(resDirs);
                        warEntries.put(entry.getId(), entry);
                        warEntriesNoVersion.put(entry.getGroupId() + "+" + entry.getArtifactId(), entry);
                    } else {
                        resDir = new File(file.getParentFile(), "src/main/resources");
                        if(resDir.exists()) {
                            List resDirs = new ArrayList();
                            resDirs.add(resDir);
                            entry.setDirectories(resDirs);
                            jarEntries.put(entry.getId(), entry);
                            jarEntriesNoVersion.put(entry.getGroupId() + "+" + entry.getArtifactId(), entry);
                        }
                    }
                }
            }
        }
    }
    

    public static class Entry {

        private String groupId;
        private String artifactId;
        private String version;
        private List directories = new ArrayList();

        public String getId() {
            return groupId + "+" + artifactId + "+" + version;
        }

        public String getGroupId() {
            return groupId;
        }

        public void setGroupId(String groupId) {
            this.groupId = groupId;
        }

        public String getArtifactId() {
            return artifactId;
        }

        public void setArtifactId(String artifactId) {
            this.artifactId = artifactId;
        }

        public String getVersion() {
            return version;
        }

        public void setVersion(String version) {
            this.version = version;
        }

        public List getDirectories() {
            return directories;
        }

        public void setDirectories(List directories) {
            this.directories = directories;
        }

    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy