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