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