001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu, jcarsique
016 */
017package org.nuxeo.connect.update.task.update;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.OutputStreamWriter;
024import java.io.Writer;
025import java.util.HashMap;
026import java.util.Map;
027
028import javax.xml.parsers.DocumentBuilder;
029import javax.xml.parsers.DocumentBuilderFactory;
030import javax.xml.parsers.ParserConfigurationException;
031
032import org.apache.commons.io.IOUtils;
033import org.nuxeo.connect.update.PackageException;
034import org.nuxeo.connect.update.xml.XmlWriter;
035import org.w3c.dom.Document;
036import org.w3c.dom.Element;
037import org.w3c.dom.Node;
038import org.xml.sax.SAXException;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public class RegistrySerializer extends XmlWriter {
044
045    private final static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
046
047    /**
048     * @since 5.7
049     */
050    public RegistrySerializer() {
051        super("  ");
052    }
053
054    /**
055     * Serializes the given registry into the given file.
056     *
057     * @param registry
058     * @param file
059     * @throws IOException
060     */
061    public static void store(Map<String, Entry> registry, File file) throws IOException {
062        RegistrySerializer serializer = new RegistrySerializer();
063        serializer.write(registry);
064        serializer.write(file);
065    }
066
067    /**
068     * De-serializes the given file into a Nuxeo packages registry
069     *
070     * @param file
071     * @return The Nuxeo packages registry described by the given file
072     */
073    public static Map<String, Entry> load(File file) throws PackageException, IOException {
074        RegistrySerializer serializer = new RegistrySerializer();
075        return serializer.read(file);
076    }
077
078    protected Map<String, Entry> read(File file) throws PackageException, IOException {
079        FileInputStream in = new FileInputStream(file);
080        try {
081            HashMap<String, Entry> registry = new HashMap<String, Entry>();
082            factory.setNamespaceAware(true);
083            DocumentBuilder builder = factory.newDocumentBuilder();
084            Document document = builder.parse(in);
085            read(document.getDocumentElement(), registry);
086            return registry;
087        } catch (ParserConfigurationException | SAXException e) {
088            throw new RuntimeException(e);
089        } finally {
090            IOUtils.closeQuietly(in);
091        }
092    }
093
094    protected void read(Element element, Map<String, Entry> registry) throws PackageException {
095        Node node = element.getFirstChild();
096        while (node != null) {
097            if (node.getNodeType() == Node.ELEMENT_NODE && "entry".equals(node.getNodeName())) {
098                Entry entry = readEntryElement((Element) node);
099                registry.put(entry.getKey(), entry);
100            }
101            node = node.getNextSibling();
102        }
103    }
104
105    protected Entry readEntryElement(Element element) throws PackageException {
106        Entry entry = new Entry(readKeyAttr(element));
107        Node node = element.getFirstChild();
108        while (node != null) {
109            if (node.getNodeType() == Node.ELEMENT_NODE) {
110                String name = node.getNodeName();
111                if ("version".equals(name)) {
112                    Version v = readVersionElement((Element) node);
113                    entry.addVersion(v);
114                } else if ("base-version".equals(name)) {
115                    Version v = readVersionElement((Element) node);
116                    entry.setBaseVersion(v);
117                }
118            }
119            node = node.getNextSibling();
120        }
121        return entry;
122    }
123
124    protected String readKeyAttr(Element element) throws PackageException {
125        String key = element.getAttribute("key");
126        if (key.length() == 0) {
127            throw new PackageException("Invalid entry. No 'key' attribute found!");
128        }
129        return key;
130    }
131
132    protected String readNameAttr(Element element) throws PackageException {
133        String version = element.getAttribute("name");
134        if (version.length() == 0) {
135            throw new PackageException("Invalid version entry. No 'name' attribute found!");
136        }
137        return version;
138    }
139
140    protected String readPathAttr(Element element) throws PackageException {
141        String path = element.getAttribute("path");
142        if (path.length() == 0) {
143            throw new PackageException("Invalid version entry. No 'path' attribute found!");
144        }
145        return path;
146    }
147
148    protected Version readVersionElement(Element element) throws PackageException {
149        Version v = new Version(readNameAttr(element));
150        v.setPath(readPathAttr(element));
151        Node node = element.getFirstChild();
152        while (node != null) {
153            if (node.getNodeType() == Node.ELEMENT_NODE && "package".equals(node.getNodeName())) {
154                UpdateOptions opt = new UpdateOptions();
155                opt.pkgId = ((Element) node).getTextContent().trim();
156                opt.upgradeOnly = Boolean.parseBoolean(((Element) node).getAttribute("upgradeOnly"));
157                v.addPackage(opt);
158            }
159            node = node.getNextSibling();
160        }
161        return v;
162    }
163
164    protected void write(Map<String, Entry> registry) {
165        writeXmlDecl();
166        start("registry");
167        startContent();
168        for (Entry entry : registry.values()) {
169            writeEntry(entry);
170        }
171        end("registry");
172    }
173
174    protected void writeEntry(Entry entry) {
175        start("entry");
176        attr("key", entry.getKey());
177        startContent();
178        if (entry.hasBaseVersion()) {
179            writeBaseVersion(entry.getBaseVersion());
180        }
181        for (Version v : entry) {
182            writeVersion(v);
183        }
184        end("entry");
185    }
186
187    protected void writeBaseVersion(Version version) {
188        start("base-version");
189        attr("name", version.getVersion());
190        attr("path", version.getPath());
191        end();
192    }
193
194    protected void writeVersion(Version version) {
195        start("version");
196        attr("name", version.getVersion());
197        attr("path", version.getPath());
198        startContent();
199        Map<String, UpdateOptions> packages = version.getPackages();
200        for (UpdateOptions opt : packages.values()) {
201            start("package");
202            if (opt.upgradeOnly) {
203                attr("upgradeOnly", "true");
204            }
205            // Missing methods to properly append the following without indent
206            text(">" + opt.pkgId + "</package>\n");
207            // startContent(false);
208            // text(opt.pkgId);
209            // end("package", false);
210        }
211        end("version");
212    }
213
214    /**
215     * @param file Output file
216     * @throws IOException
217     * @since 5.7
218     */
219    protected void write(File file) throws IOException {
220        Writer writer = new OutputStreamWriter(new FileOutputStream(file));
221        try {
222            writer.write(sb.toString());
223        } finally {
224            IOUtils.closeQuietly(writer);
225        }
226    }
227
228}