001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.runtime.model.persistence.fs;
013
014import java.io.ByteArrayInputStream;
015import java.io.File;
016import java.io.IOException;
017import java.util.ArrayList;
018import java.util.List;
019
020import javax.xml.parsers.DocumentBuilder;
021import javax.xml.parsers.DocumentBuilderFactory;
022import javax.xml.parsers.ParserConfigurationException;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.common.Environment;
027import org.nuxeo.common.utils.FileUtils;
028import org.nuxeo.common.xmap.DOMSerializer;
029import org.nuxeo.runtime.model.persistence.Contribution;
030import org.nuxeo.runtime.model.persistence.ContributionStorage;
031import org.w3c.dom.DOMException;
032import org.w3c.dom.Document;
033import org.w3c.dom.Element;
034import org.w3c.dom.Node;
035import org.xml.sax.SAXException;
036
037/**
038 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
039 */
040public class FileSystemStorage implements ContributionStorage {
041
042    public static final Log log = LogFactory.getLog(FileSystemStorage.class);
043
044    protected static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
045
046    protected final File root;
047
048    public FileSystemStorage() {
049        root = new File(Environment.getDefault().getData(), "contribs");
050        root.mkdirs();
051    }
052
053    public static synchronized String safeRead(File file) {
054        try {
055            return FileUtils.readFile(file);
056        } catch (IOException e) {
057            throw new RuntimeException(e);
058        }
059    }
060
061    public static synchronized void safeWrite(File file, String content) {
062        try {
063            FileUtils.writeFile(file, content);
064        } catch (IOException e) {
065            throw new RuntimeException(e);
066        }
067    }
068
069    public static synchronized boolean safeCreate(File file, String content) {
070        if (file.isFile()) {
071            return false;
072        }
073        try {
074            FileUtils.writeFile(file, content);
075        } catch (IOException e) {
076            throw new RuntimeException(e);
077        }
078        return true;
079    }
080
081    public static synchronized boolean safeRemove(File file) {
082        return file.delete();
083    }
084
085    public static void loadMetadata(Contribution contrib) {
086        try {
087            DocumentBuilder docBuilder = factory.newDocumentBuilder();
088            Document doc = docBuilder.parse(new ByteArrayInputStream(contrib.getContent().getBytes()));
089            Element root = doc.getDocumentElement();
090            contrib.setDisabled(Boolean.parseBoolean(root.getAttribute("disabled")));
091            Node node = root.getFirstChild();
092            while (node != null) {
093                if (node.getNodeType() == Node.ELEMENT_NODE && "documentation".equals(node.getNodeName())) {
094                    break;
095                }
096                node = node.getNextSibling();
097            }
098            if (node != null) {
099                node = node.getFirstChild();
100                StringBuilder buf = new StringBuilder();
101                while (node != null) {
102                    if (node.getNodeType() == Node.TEXT_NODE) {
103                        buf.append(node.getNodeValue());
104                    }
105                    node = node.getNextSibling();
106                }
107                contrib.setDescription(buf.toString().trim());
108            } else {
109                contrib.setDescription("");
110            }
111        } catch (ParserConfigurationException | SAXException | IOException | DOMException e) {
112            log.error("Failed to read contribution metadata", e);
113        }
114    }
115
116    @Override
117    public Contribution addContribution(Contribution contribution) {
118        File file = new File(root, contribution.getName() + ".xml");
119        String content = contribution.getContent();
120        if (safeCreate(file, content)) {
121            return new ContributionFile(contribution.getName(), file);
122        }
123        return null;
124    }
125
126    @Override
127    public Contribution getContribution(String name) {
128        File file = new File(root, name + ".xml");
129        if (file.isFile()) {
130            return new ContributionFile(name, file);
131        }
132        return null;
133    }
134
135    @Override
136    public List<Contribution> getContributions() {
137        List<Contribution> result = new ArrayList<Contribution>();
138        File[] files = root.listFiles();
139        if (files == null) {
140            return result;
141        }
142        for (File file : files) {
143            String name = file.getName();
144            if (name.endsWith(".xml")) {
145                name = name.substring(0, name.length() - 4);
146                result.add(new ContributionFile(name, file));
147            }
148        }
149        return result;
150    }
151
152    @Override
153    public boolean removeContribution(Contribution contrib) {
154        return safeRemove(new File(root, contrib.getName() + ".xml"));
155    }
156
157    @Override
158    public Contribution updateContribution(Contribution contribution) {
159        File file = new File(root, contribution.getName() + ".xml");
160        String content = safeRead(file);
161        DocumentBuilder docBuilder;
162        try {
163            docBuilder = factory.newDocumentBuilder();
164        } catch (ParserConfigurationException e) {
165            throw new RuntimeException(e);
166        }
167        Document doc;
168        try {
169            doc = docBuilder.parse(new ByteArrayInputStream(content.getBytes()));
170        } catch (SAXException | IOException e) {
171            throw new RuntimeException(e);
172        }
173        Element root = doc.getDocumentElement();
174        if (contribution.isDisabled()) {
175            root.setAttribute("disabled", "true");
176        } else {
177            root.removeAttribute("disabled");
178        }
179        Node node = root.getFirstChild();
180        while (node != null) {
181            if (node.getNodeType() == Node.ELEMENT_NODE && "documentation".equals(node.getNodeName())) {
182                break;
183            }
184            node = node.getNextSibling();
185        }
186        String description = contribution.getDescription();
187        if (description == null) {
188            description = "";
189        }
190        if (node != null) {
191            root.removeChild(node);
192        }
193        Element el = doc.createElement("documentation");
194        el.appendChild(doc.createTextNode(description));
195        root.appendChild(el);
196
197        try {
198            safeWrite(file, DOMSerializer.toString(doc));
199        } catch (IOException e) {
200            throw new RuntimeException(e);
201        }
202        return getContribution(contribution.getName());
203    }
204
205}