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