001/* 002 * (C) Copyright 2006-2010 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.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 * Thierry Delprat 016 */ 017package org.nuxeo.apidoc.browse; 018 019import java.io.File; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026import java.util.Map; 027 028import javax.naming.NamingException; 029import javax.transaction.HeuristicMixedException; 030import javax.transaction.HeuristicRollbackException; 031import javax.transaction.NotSupportedException; 032import javax.transaction.RollbackException; 033import javax.transaction.SystemException; 034import javax.transaction.UserTransaction; 035import javax.ws.rs.GET; 036import javax.ws.rs.POST; 037import javax.ws.rs.Path; 038import javax.ws.rs.PathParam; 039import javax.ws.rs.Produces; 040import javax.ws.rs.WebApplicationException; 041import javax.ws.rs.core.Response; 042 043import org.apache.commons.logging.Log; 044import org.apache.commons.logging.LogFactory; 045import org.nuxeo.apidoc.documentation.DocumentationService; 046import org.nuxeo.apidoc.export.ArchiveFile; 047import org.nuxeo.apidoc.snapshot.DistributionSnapshot; 048import org.nuxeo.apidoc.snapshot.DistributionSnapshotDesc; 049import org.nuxeo.apidoc.snapshot.SnapshotFilter; 050import org.nuxeo.apidoc.snapshot.SnapshotManager; 051import org.nuxeo.apidoc.snapshot.SnapshotManagerComponent; 052import org.nuxeo.apidoc.snapshot.SnapshotResolverHelper; 053import org.nuxeo.ecm.automation.OperationException; 054import org.nuxeo.ecm.core.api.Blob; 055import org.nuxeo.ecm.core.api.DocumentModel; 056import org.nuxeo.ecm.core.api.NuxeoException; 057import org.nuxeo.ecm.core.api.NuxeoPrincipal; 058import org.nuxeo.ecm.webengine.forms.FormData; 059import org.nuxeo.ecm.webengine.model.Resource; 060import org.nuxeo.ecm.webengine.model.WebObject; 061import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException; 062import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; 063import org.nuxeo.runtime.api.Framework; 064import org.nuxeo.runtime.transaction.TransactionHelper; 065 066@Path("/distribution") 067// needed for 5.4.1 068@WebObject(type = "distribution") 069public class Distribution extends ModuleRoot { 070 071 public static final String DIST_ID = "distId"; 072 073 protected static final Log log = LogFactory.getLog(Distribution.class); 074 075 // handle errors 076 @Override 077 public Object handleError(WebApplicationException e) { 078 if (e instanceof WebResourceNotFoundException) { 079 return Response.status(404).entity(getTemplate("error/error_404.ftl")).type("text/html").build(); 080 } else { 081 return super.handleError(e); 082 } 083 } 084 085 protected SnapshotManager getSnapshotManager() { 086 return Framework.getLocalService(SnapshotManager.class); 087 } 088 089 public String getNavigationPoint() { 090 String currentUrl = getContext().getURL(); 091 String navPoint = "somewhere"; 092 093 if (currentUrl.contains("/listBundles")) { 094 navPoint = "listBundles"; 095 } else if (currentUrl.contains("/listSeamComponents")) { 096 navPoint = "listSeamComponents"; 097 } else if (currentUrl.contains("/viewSeamComponent")) { 098 navPoint = "viewSeamComponent"; 099 } else if (currentUrl.contains("/listComponents")) { 100 navPoint = "listComponents"; 101 } else if (currentUrl.contains("/listServices")) { 102 navPoint = "listServices"; 103 } else if (currentUrl.contains("/listExtensionPoints")) { 104 navPoint = "listExtensionPoints"; 105 } else if (currentUrl.contains("/listContributions")) { 106 navPoint = "listContributions"; 107 } else if (currentUrl.contains("/listBundleGroups")) { 108 navPoint = "listBundleGroups"; 109 } else if (currentUrl.contains("/viewBundleGroup")) { 110 navPoint = "viewBundleGroup"; 111 } else if (currentUrl.contains("/viewComponent")) { 112 navPoint = "viewComponent"; 113 } else if (currentUrl.contains("/viewService")) { 114 navPoint = "viewService"; 115 } else if (currentUrl.contains("/viewExtensionPoint")) { 116 navPoint = "viewExtensionPoint"; 117 } else if (currentUrl.contains("/viewContribution")) { 118 navPoint = "viewContribution"; 119 } else if (currentUrl.contains("/viewBundle")) { 120 navPoint = "viewBundle"; 121 } else if (currentUrl.contains("/listOperations")) { 122 navPoint = "listOperations"; 123 } else if (currentUrl.contains("/viewOperation")) { 124 navPoint = "viewOperation"; 125 } else if (currentUrl.contains("/doc")) { 126 navPoint = "documentation"; 127 } 128 return navPoint; 129 } 130 131 @GET 132 @Produces("text/html") 133 public Object doGet() { 134 return getView("index").arg("hideNav", Boolean.TRUE); 135 } 136 137 @Path("latest") 138 public Resource getLatest() { 139 List<DistributionSnapshot> snaps = getSnapshotManager().listPersistentSnapshots((ctx.getCoreSession())); 140 141 List<String> keys = new ArrayList<String>(); 142 for (DistributionSnapshot snap : snaps) { 143 if (snap.getName().equalsIgnoreCase("Nuxeo Platform")) { 144 keys.add(snap.getKey()); 145 } 146 Collections.sort(keys); 147 Collections.reverse(keys); 148 } 149 150 String latest = "current"; 151 if (keys.size() > 0) { 152 latest = keys.get(0); 153 } 154 return ctx.newObject("redirectWO", "latest", latest); 155 } 156 157 @Path("{distributionId}") 158 public Resource viewDistribution(@PathParam("distributionId") String distributionId) { 159 if (distributionId == null || "".equals(distributionId)) { 160 return this; 161 } 162 String orgDistributionId = distributionId; 163 Boolean embeddedMode = Boolean.FALSE; 164 if ("adm".equals(distributionId)) { 165 embeddedMode = Boolean.TRUE; 166 } else { 167 List<DistributionSnapshot> snaps = getSnapshotManager().listPersistentSnapshots((ctx.getCoreSession())); 168 snaps.add(getSnapshotManager().getRuntimeSnapshot()); 169 distributionId = SnapshotResolverHelper.findBestMatch(snaps, distributionId); 170 } 171 if (distributionId == null || "".equals(distributionId)) { 172 distributionId = "current"; 173 } 174 175 if (!orgDistributionId.equals(distributionId)) { 176 return ctx.newObject("redirectWO", orgDistributionId, distributionId); 177 } 178 179 ctx.setProperty("embeddedMode", embeddedMode); 180 ctx.setProperty("distribution", getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession())); 181 ctx.setProperty(DIST_ID, distributionId); 182 return ctx.newObject("apibrowser", distributionId, embeddedMode); 183 } 184 185 public List<DistributionSnapshotDesc> getAvailableDistributions() { 186 return getSnapshotManager().getAvailableDistributions(ctx.getCoreSession()); 187 } 188 189 public String getRuntimeDistributionName() { 190 return SnapshotManagerComponent.RUNTIME; 191 } 192 193 public DistributionSnapshot getRuntimeDistribution() { 194 return getSnapshotManager().getRuntimeSnapshot(); 195 } 196 197 public List<DistributionSnapshot> listPersistedDistributions() { 198 return getSnapshotManager().listPersistentSnapshots(ctx.getCoreSession()); 199 } 200 201 public Map<String, DistributionSnapshot> getPersistedDistributions() { 202 return getSnapshotManager().getPersistentSnapshots(ctx.getCoreSession()); 203 } 204 205 public DistributionSnapshot getCurrentDistribution() { 206 String distId = (String) ctx.getProperty(DIST_ID); 207 DistributionSnapshot currentDistribution = (DistributionSnapshot) ctx.getProperty("currentDistribution"); 208 if (currentDistribution == null || !currentDistribution.getKey().equals(distId)) { 209 currentDistribution = getSnapshotManager().getSnapshot(distId, ctx.getCoreSession()); 210 ctx.setProperty("currentDistribution", currentDistribution); 211 } 212 return currentDistribution; 213 } 214 215 @POST 216 @Path("save") 217 @Produces("text/html") 218 public Object doSave() throws NamingException, NotSupportedException, SystemException, OperationException, 219 RollbackException, HeuristicMixedException, HeuristicRollbackException { 220 if (!isEditor()) { 221 return null; 222 } 223 FormData formData = getContext().getForm(); 224 String distribLabel = formData.getString("name"); 225 226 log.info("Start Snapshot..."); 227 boolean startedTx = false; 228 UserTransaction tx = TransactionHelper.lookupUserTransaction(); 229 if (tx != null && !TransactionHelper.isTransactionActiveOrMarkedRollback()) { 230 tx.begin(); 231 startedTx = true; 232 } 233 try { 234 getSnapshotManager().persistRuntimeSnapshot(getContext().getCoreSession(), distribLabel); 235 } catch (NuxeoException e) { 236 log.error("Error during storage", e); 237 if (tx != null) { 238 tx.rollback(); 239 } 240 return getView("savedKO").arg("message", e.getMessage()); 241 } 242 log.info("Snapshot saved."); 243 if (tx != null && startedTx) { 244 tx.commit(); 245 } 246 247 String redirectUrl = getContext().getBaseURL() + getPath(); 248 log.debug("Path => " + redirectUrl); 249 return getView("saved"); 250 } 251 252 @POST 253 @Path("saveExtended") 254 @Produces("text/html") 255 public Object doSaveExtended() throws NamingException, NotSupportedException, SystemException, OperationException, 256 SecurityException, RollbackException, HeuristicMixedException, HeuristicRollbackException { 257 if (!isEditor()) { 258 return null; 259 } 260 261 FormData formData = getContext().getForm(); 262 263 String distribLabel = formData.getString("name"); 264 String bundleList = formData.getString("bundles"); 265 String pkgList = formData.getString("packages"); 266 SnapshotFilter filter = new SnapshotFilter(distribLabel); 267 268 if (bundleList != null) { 269 String[] bundles = bundleList.split("\n"); 270 for (String bundleId : bundles) { 271 filter.addBundlePrefix(bundleId); 272 } 273 } 274 275 if (pkgList != null) { 276 String[] packages = pkgList.split("\\r?\\n"); 277 for (String pkg : packages) { 278 filter.addPackagesPrefix(pkg); 279 } 280 } 281 282 log.info("Start Snapshot..."); 283 boolean startedTx = false; 284 UserTransaction tx = TransactionHelper.lookupUserTransaction(); 285 if (tx != null && !TransactionHelper.isTransactionActiveOrMarkedRollback()) { 286 tx.begin(); 287 startedTx = true; 288 } 289 try { 290 getSnapshotManager().persistRuntimeSnapshot(getContext().getCoreSession(), distribLabel, filter); 291 } catch (NuxeoException e) { 292 log.error("Error during storage", e); 293 if (tx != null) { 294 tx.rollback(); 295 } 296 return getView("savedKO").arg("message", e.getMessage()); 297 } 298 log.info("Snapshot saved."); 299 if (tx != null && startedTx) { 300 tx.commit(); 301 } 302 return getView("saved"); 303 } 304 305 public String getDocumentationInfo() { 306 DocumentationService ds = Framework.getService(DocumentationService.class); 307 return ds.getDocumentationStats(getContext().getCoreSession()); 308 } 309 310 protected File getExportTmpFile() { 311 String fPath = System.getProperty("java.io.tmpdir") + "/export.zip"; 312 File tmpFile = new File(fPath); 313 if (tmpFile.exists()) { 314 tmpFile.delete(); 315 tmpFile = new File(fPath); 316 } 317 tmpFile.deleteOnExit(); 318 return tmpFile; 319 } 320 321 @GET 322 @Path("downloadDoc") 323 public Response downloadDoc() throws IOException { 324 DocumentationService ds = Framework.getService(DocumentationService.class); 325 File tmp = getExportTmpFile(); 326 tmp.createNewFile(); 327 OutputStream out = new FileOutputStream(tmp); 328 ds.exportDocumentation(getContext().getCoreSession(), out); 329 out.flush(); 330 out.close(); 331 ArchiveFile aFile = new ArchiveFile(tmp.getAbsolutePath()); 332 return Response.ok(aFile).header("Content-Disposition", "attachment;filename=" + "nuxeo-documentation.zip").type( 333 "application/zip").build(); 334 } 335 336 @GET 337 @Path("download/{distributionId}") 338 public Response downloadDistrib(@PathParam("distributionId") String distribId) throws IOException { 339 File tmp = getExportTmpFile(); 340 tmp.createNewFile(); 341 OutputStream out = new FileOutputStream(tmp); 342 getSnapshotManager().exportSnapshot(getContext().getCoreSession(), distribId, out); 343 out.close(); 344 String fName = "nuxeo-distribution-" + distribId + ".zip"; 345 fName = fName.replace(" ", "_"); 346 ArchiveFile aFile = new ArchiveFile(tmp.getAbsolutePath()); 347 return Response.ok(aFile).header("Content-Disposition", "attachment;filename=" + fName).type("application/zip").build(); 348 } 349 350 @POST 351 @Path("uploadDistrib") 352 @Produces("text/html") 353 public Object uploadDistrib() throws IOException { 354 if (!isEditor()) { 355 return null; 356 } 357 Blob blob = getContext().getForm().getFirstBlob(); 358 359 getSnapshotManager().importSnapshot(getContext().getCoreSession(), blob.getStream()); 360 getSnapshotManager().readPersistentSnapshots(getContext().getCoreSession()); 361 362 return getView("index"); 363 } 364 365 @POST 366 @Path("uploadDistribTmp") 367 @Produces("text/html") 368 public Object uploadDistribTmp() throws IOException { 369 if (!isEditor()) { 370 return null; 371 } 372 Blob blob = getContext().getForm().getFirstBlob(); 373 if (blob == null || blob.getLength() == 0) { 374 return null; 375 } 376 DocumentModel snap = getSnapshotManager().importTmpSnapshot(getContext().getCoreSession(), blob.getStream()); 377 if (snap == null) { 378 log.error("Unable to import archive"); 379 return null; 380 } 381 DistributionSnapshot snapObject = snap.getAdapter(DistributionSnapshot.class); 382 return getView("uploadEdit").arg("tmpSnap", snap).arg("snapObject", snapObject); 383 } 384 385 @POST 386 @Path("uploadDistribTmpValid") 387 @Produces("text/html") 388 public Object uploadDistribTmpValid() { 389 if (!isEditor()) { 390 return null; 391 } 392 393 FormData formData = getContext().getForm(); 394 String name = formData.getString("name"); 395 String version = formData.getString("version"); 396 String pathSegment = formData.getString("pathSegment"); 397 String title = formData.getString("title"); 398 399 getSnapshotManager().validateImportedSnapshot(getContext().getCoreSession(), name, version, pathSegment, title); 400 getSnapshotManager().readPersistentSnapshots(getContext().getCoreSession()); 401 return getView("importDone"); 402 } 403 404 @POST 405 @Path("uploadDoc") 406 @Produces("text/html") 407 public Object uploadDoc() throws IOException { 408 if (!isEditor()) { 409 return null; 410 } 411 412 Blob blob = getContext().getForm().getFirstBlob(); 413 if (blob == null || blob.getLength() == 0) { 414 return null; 415 } 416 417 DocumentationService ds = Framework.getService(DocumentationService.class); 418 ds.importDocumentation(getContext().getCoreSession(), blob.getStream()); 419 420 log.info("Documents imported."); 421 422 return getView("docImportDone"); 423 } 424 425 public boolean isEmbeddedMode() { 426 Boolean embed = (Boolean) getContext().getProperty("embeddedMode", Boolean.FALSE); 427 return embed == null ? false : embed.booleanValue(); 428 } 429 430 public boolean isEditor() { 431 if (isEmbeddedMode()) { 432 return false; 433 } 434 NuxeoPrincipal principal = (NuxeoPrincipal) getContext().getPrincipal(); 435 return SecurityHelper.canEditDocumentation(principal); 436 } 437 438}