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