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:[email protected]">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}