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}