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