001/*
002 * (C) Copyright 2006-2014 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-2.1.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 *     Bogdan Stefanescu
016 *     Mathieu Guillaume
017 */
018package org.nuxeo.connect.update.util;
019
020import java.io.ByteArrayInputStream;
021import java.io.File;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.util.ArrayList;
026import java.util.LinkedHashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.zip.ZipEntry;
030import java.util.zip.ZipOutputStream;
031
032import org.apache.commons.io.IOUtils;
033
034import org.nuxeo.common.utils.FileUtils;
035import org.nuxeo.connect.update.LocalPackage;
036import org.nuxeo.connect.update.NuxeoValidationState;
037import org.nuxeo.connect.update.PackageDependency;
038import org.nuxeo.connect.update.PackageType;
039import org.nuxeo.connect.update.PackageVisibility;
040import org.nuxeo.connect.update.ProductionState;
041import org.nuxeo.connect.update.Version;
042import org.nuxeo.connect.update.model.PackageDefinition;
043import org.nuxeo.connect.update.model.TaskDefinition;
044import org.nuxeo.connect.update.xml.FormDefinition;
045import org.nuxeo.connect.update.xml.FormsDefinition;
046import org.nuxeo.connect.update.xml.PackageDefinitionImpl;
047import org.nuxeo.connect.update.xml.TaskDefinitionImpl;
048import org.nuxeo.connect.update.xml.XmlSerializer;
049import org.nuxeo.runtime.api.Framework;
050
051/**
052 * Build an XML representation of a package.
053 *
054 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
055 */
056public class PackageBuilder {
057
058    protected final PackageDefinition def;
059
060    protected final List<FormDefinition> installForms;
061
062    protected final List<FormDefinition> uninstallForms;
063
064    protected final List<FormDefinition> validationForms;
065
066    protected final List<String> platforms;
067
068    protected final List<PackageDependency> dependencies;
069
070    protected final List<PackageDependency> conflicts;
071
072    protected final List<PackageDependency> provides;
073
074    protected final LinkedHashMap<String, InputStream> entries;
075
076    public PackageBuilder() {
077        def = new PackageDefinitionImpl();
078        platforms = new ArrayList<>();
079        dependencies = new ArrayList<>();
080        conflicts = new ArrayList<>();
081        provides = new ArrayList<>();
082        entries = new LinkedHashMap<>();
083        installForms = new ArrayList<>();
084        validationForms = new ArrayList<>();
085        uninstallForms = new ArrayList<>();
086    }
087
088    public PackageBuilder name(String name) {
089        def.setName(name);
090        return this;
091    }
092
093    public PackageBuilder version(Version version) {
094        def.setVersion(version);
095        return this;
096    }
097
098    public PackageBuilder version(String version) {
099        def.setVersion(new Version(version));
100        return this;
101    }
102
103    public PackageBuilder type(String type) {
104        def.setType(PackageType.getByValue(type));
105        return this;
106    }
107
108    public PackageBuilder type(PackageType type) {
109        def.setType(type);
110        return this;
111    }
112
113    /**
114     * @since 5.6
115     */
116    public PackageBuilder visibility(String visibility) {
117        return visibility(PackageVisibility.valueOf(visibility));
118    }
119
120    /**
121     * @since 5.6
122     */
123    public PackageBuilder visibility(PackageVisibility visibility) {
124        try {
125            def.getClass().getMethod("setVisibility", PackageVisibility.class);
126            def.setVisibility(visibility);
127        } catch (NoSuchMethodException e) {
128            // Ignore visibility with old Connect Client versions
129        }
130        return this;
131    }
132
133    public PackageBuilder title(String title) {
134        def.setTitle(title);
135        return this;
136    }
137
138    public PackageBuilder description(String description) {
139        def.setDescription(description);
140        return this;
141    }
142
143    public PackageBuilder classifier(String classifier) {
144        def.setClassifier(classifier);
145        return this;
146    }
147
148    public PackageBuilder vendor(String vendor) {
149        def.setVendor(vendor);
150        return this;
151    }
152
153    public PackageBuilder homePage(String homePage) {
154        def.setHomePage(homePage);
155        return this;
156    }
157
158    public PackageBuilder installer(TaskDefinition task) {
159        def.setInstaller(task);
160        return this;
161    }
162
163    public PackageBuilder installer(String type, boolean restart) {
164        def.setInstaller(new TaskDefinitionImpl(type, restart));
165        return this;
166    }
167
168    public PackageBuilder uninstaller(TaskDefinition task) {
169        def.setUninstaller(task);
170        return this;
171    }
172
173    public PackageBuilder uninstaller(String type, boolean restart) {
174        def.setUninstaller(new TaskDefinitionImpl(type, restart));
175        return this;
176    }
177
178    public PackageBuilder validationState(NuxeoValidationState validationState) {
179        try {
180            def.getClass().getMethod("setValidationState", NuxeoValidationState.class);
181            def.setValidationState(validationState);
182        } catch (NoSuchMethodException e) {
183            // Ignore setValidationState with old Connect Client versions
184        }
185        return this;
186    }
187
188    public PackageBuilder productionState(ProductionState productionState) {
189        try {
190            def.getClass().getMethod("setProductionState", ProductionState.class);
191            def.setProductionState(productionState);
192        } catch (NoSuchMethodException e) {
193            // Ignore setProductionState with old Connect Client versions
194        }
195        return this;
196    }
197
198    public PackageBuilder supported(boolean supported) {
199        try {
200            def.getClass().getMethod("setSupported", boolean.class);
201            def.setSupported(supported);
202        } catch (NoSuchMethodException e) {
203            // Ignore setSupported with old Connect Client versions
204        }
205        return this;
206    }
207
208    public PackageBuilder hotReloadSupport(boolean hotReloadSupport) {
209        try {
210            def.getClass().getMethod("setHotReloadSupport", boolean.class);
211            def.setHotReloadSupport(hotReloadSupport);
212        } catch (NoSuchMethodException e) {
213            // Ignore setHotReloadSupport with old Connect Client versions
214        }
215        return this;
216    }
217
218    public PackageBuilder requireTermsAndConditionsAcceptance(boolean requireTermsAndConditionsAcceptance) {
219        try {
220            def.getClass().getMethod("setRequireTermsAndConditionsAcceptance", boolean.class);
221            def.setRequireTermsAndConditionsAcceptance(requireTermsAndConditionsAcceptance);
222        } catch (NoSuchMethodException e) {
223            // Ignore setRequireTermsAndConditionsAcceptance with old Connect
224            // Client versions
225        }
226        return this;
227    }
228
229    public PackageBuilder validator(String validator) {
230        def.setValidator(validator);
231        return this;
232    }
233
234    public PackageBuilder platform(String platform) {
235        platforms.add(platform);
236        return this;
237    }
238
239    public PackageBuilder dependency(String expr) {
240        dependencies.add(new PackageDependency(expr));
241        return this;
242    }
243
244    public PackageBuilder conflict(String expr) {
245        conflicts.add(new PackageDependency(expr));
246        return this;
247    }
248
249    public PackageBuilder provide(String expr) {
250        provides.add(new PackageDependency(expr));
251        return this;
252    }
253
254    public PackageBuilder addInstallForm(FormDefinition form) {
255        installForms.add(form);
256        return this;
257    }
258
259    public PackageBuilder addUninstallForm(FormDefinition form) {
260        uninstallForms.add(form);
261        return this;
262    }
263
264    public PackageBuilder addValidationForm(FormDefinition form) {
265        validationForms.add(form);
266        return this;
267    }
268
269    public PackageBuilder addLicense(String content) {
270        return addLicense(new ByteArrayInputStream(content.getBytes()));
271    }
272
273    public PackageBuilder addLicense(InputStream in) {
274        return addEntry(LocalPackage.LICENSE, in);
275    }
276
277    public PackageBuilder addInstallScript(String content) {
278        return addInstallScript(new ByteArrayInputStream(content.getBytes()));
279    }
280
281    public PackageBuilder addInstallScript(InputStream in) {
282        return addEntry(LocalPackage.INSTALL, in);
283    }
284
285    public PackageBuilder addUninstallScript(String content) {
286        return addUninstallScript(new ByteArrayInputStream(content.getBytes()));
287    }
288
289    public PackageBuilder addUninstallScript(InputStream in) {
290        return addEntry(LocalPackage.UNINSTALL, in);
291    }
292
293    public PackageBuilder addTermsAndConditions(String content) {
294        return addTermsAndConditions(new ByteArrayInputStream(content.getBytes()));
295    }
296
297    public PackageBuilder addTermsAndConditions(InputStream in) {
298        return addEntry(LocalPackage.TERMSANDCONDITIONS, in);
299    }
300
301    /**
302     * The entry content will be copied into the zip at build time and the given input stream will be closed. (event if
303     * an exception occurs) - so you don't need to handle stream closing.
304     */
305    public PackageBuilder addEntry(String path, InputStream in) {
306        entries.put(path, in);
307        return this;
308    }
309
310    public String buildManifest() {
311        if (!platforms.isEmpty()) {
312            def.setTargetPlatforms(platforms.toArray(new String[platforms.size()]));
313        }
314        if (!dependencies.isEmpty()) {
315            def.setDependencies(dependencies.toArray(new PackageDependency[dependencies.size()]));
316        }
317        if (!conflicts.isEmpty()) {
318            def.setConflicts(conflicts.toArray(new PackageDependency[conflicts.size()]));
319        }
320        if (!provides.isEmpty()) {
321            def.setProvides(provides.toArray(new PackageDependency[provides.size()]));
322        }
323        return new XmlSerializer().toXML(def);
324    }
325
326    public File build() throws IOException {
327        try {
328            String mf = buildManifest();
329            File file = File.createTempFile(def.getId(), ".zip");
330            Framework.trackFile(file, file);
331            ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(file));
332            try {
333                ZipEntry entry = new ZipEntry(LocalPackage.MANIFEST);
334                zout.putNextEntry(entry);
335                zout.write(mf.getBytes());
336                zout.closeEntry();
337                for (Map.Entry<String, InputStream> stream : entries.entrySet()) {
338                    entry = new ZipEntry(stream.getKey());
339                    zout.putNextEntry(entry);
340                    FileUtils.copy(stream.getValue(), zout);
341                    zout.closeEntry();
342                }
343                if (!installForms.isEmpty()) {
344                    addForms(installForms, LocalPackage.INSTALL_FORMS, zout);
345                }
346                if (!uninstallForms.isEmpty()) {
347                    addForms(uninstallForms, LocalPackage.UNINSTALL_FORMS, zout);
348                }
349                if (!validationForms.isEmpty()) {
350                    addForms(validationForms, LocalPackage.VALIDATION_FORMS, zout);
351                }
352            } finally {
353                zout.close();
354            }
355            return file;
356        } finally { // close streams
357            for (InputStream in : entries.values()) {
358                IOUtils.closeQuietly(in);
359            }
360        }
361    }
362
363    protected void addForms(List<FormDefinition> formDefs, String path, ZipOutputStream zout) throws IOException {
364        int i = 0;
365        FormsDefinition forms = new FormsDefinition();
366        FormDefinition[] ar = new FormDefinition[formDefs.size()];
367        for (FormDefinition form : formDefs) {
368            ar[i++] = form;
369        }
370        forms.setForms(ar);
371        String xml = new XmlSerializer().toXML(forms);
372        ZipEntry entry = new ZipEntry(path);
373        zout.putNextEntry(entry);
374        FileUtils.copy(new ByteArrayInputStream(xml.getBytes()), zout);
375        zout.closeEntry();
376    }
377
378}