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}