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