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}