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