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