001/*
002 * (C) Copyright 2006-2010 Nuxeo SAS (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 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019
020package org.nuxeo.theme.bank;
021
022import java.io.File;
023import java.io.IOException;
024import java.security.Principal;
025import java.util.Date;
026import java.util.List;
027import java.util.Properties;
028
029import javax.ws.rs.GET;
030import javax.ws.rs.POST;
031import javax.ws.rs.Path;
032import javax.ws.rs.PathParam;
033import javax.ws.rs.Produces;
034import javax.ws.rs.WebApplicationException;
035import javax.ws.rs.core.MediaType;
036import javax.ws.rs.core.Response;
037
038import org.apache.commons.logging.Log;
039import org.apache.commons.logging.LogFactory;
040import org.nuxeo.common.utils.FileUtils;
041import org.nuxeo.ecm.core.api.NuxeoPrincipal;
042import org.nuxeo.ecm.webengine.WebException;
043import org.nuxeo.ecm.webengine.model.WebObject;
044import org.nuxeo.ecm.webengine.model.exceptions.WebSecurityException;
045import org.nuxeo.ecm.webengine.model.impl.ModuleRoot;
046import org.nuxeo.theme.presets.PaletteParser;
047import org.nuxeo.theme.resources.BankManager;
048import org.nuxeo.theme.resources.BankUtils;
049
050import com.sun.jersey.api.NotFoundException;
051
052@Path("/theme-banks")
053@WebObject(type = "theme-banks")
054@Produces(MediaType.TEXT_HTML)
055public class Main extends ModuleRoot {
056
057    private static final Log log = LogFactory.getLog(Main.class);
058
059    private static final String SERVER_ID = "Nuxeo/ThemeBank-1.0";
060
061    @GET
062    public Object getIndex() {
063        return getTemplate("index.ftl");
064    }
065
066    public boolean isAdministrator() {
067        Principal principal = ctx.getPrincipal();
068        if (principal == null) {
069            return false;
070        }
071        return ((NuxeoPrincipal) principal).isAdministrator();
072    }
073
074    /*
075     * Management mode
076     */
077    @Path("{bank}/manage")
078    public Object getManagement(@PathParam("bank") String bank) {
079        return newObject("Management", bank);
080    }
081
082    /*
083     * Banks
084     */
085    @GET
086    @Path("{bank}")
087    public Object displayBank(@PathParam("bank") String bank) {
088        return getTemplate("index.ftl").arg("bank", bank);
089    }
090
091    @GET
092    @Path("{bank}/view")
093    public Object displayBankView(@PathParam("bank") String bank) {
094        return getTemplate("bank.ftl").arg("bank", bank);
095    }
096
097    @GET
098    @Path("{bank}/status")
099    public Object getBankStatus(@PathParam("bank") String bank) {
100        return "OK";
101    }
102
103    @GET
104    @Path("{bank}/logo")
105    public Object displayBankLogo(@PathParam("bank") String bank) {
106        File file;
107        try {
108            file = BankManager.getBankLogoFile(bank);
109        } catch (IOException e) {
110            throw new ThemeBankException(e.getMessage(), e);
111        }
112        if (file == null || !file.exists()) {
113            return noPreview();
114        }
115        String ext = FileUtils.getFileExtension(path);
116        String mimeType = ctx.getEngine().getMimeType(ext);
117        if (mimeType == null) {
118            mimeType = "application/octet-stream";
119        }
120        return Response.ok().entity(Utils.streamFile(file)).lastModified(new Date(file.lastModified())).header(
121                "Cache-Control", "public").header("Server", SERVER_ID).type(mimeType).build();
122    }
123
124    /*
125     * UI
126     */
127    @GET
128    @Path("navtree")
129    public Object getNavtreeView() {
130        return getTemplate("navtree.ftl");
131    }
132
133    @GET
134    @Path("actionbar")
135    public Object getActionBarView() {
136        return getTemplate("actionbar.ftl");
137    }
138
139    @GET
140    @Path("banks")
141    public Object getBanksView() {
142        return getTemplate("banks.ftl");
143    }
144
145    @GET
146    @Path("session/login")
147    public Object getSessionView() {
148        return getTemplate("session.ftl");
149    }
150
151    @GET
152    @Path("session")
153    public Object doSession() {
154        Object failed = ctx.getProperty("failed");
155        if (failed == null) {
156            return getIndex();
157        }
158        return getSessionView();
159    }
160
161    @POST
162    @Path("session/@@login")
163    public Object login() {
164        return getIndex();
165    }
166
167    /*
168     * Styles
169     */
170    @GET
171    @Produces(MediaType.APPLICATION_JSON)
172    @Path("{bank}/json/styles")
173    public String listBankStyles(@PathParam("bank") String bankName) {
174        try {
175            return Utils.listBankStyles(bankName);
176        } catch (IOException e) {
177            throw new ThemeBankException(e.getMessage(), e);
178        }
179    }
180
181    @GET
182    @Produces(MediaType.APPLICATION_JSON)
183    @Path("{bank}/json/skins")
184    public String listBankSkins(@PathParam("bank") String bankName) {
185        try {
186            return Utils.listBankSkins(bankName);
187        } catch (IOException e) {
188            throw new ThemeBankException(e.getMessage(), e);
189        }
190    }
191
192    @GET
193    @Produces(MediaType.APPLICATION_JSON)
194    @Path("{bank}/json/presets")
195    public String listBankPresets(@PathParam("bank") String bankName) {
196        try {
197            return Utils.listBankPresets(bankName);
198        } catch (IOException e) {
199            throw new ThemeBankException(e.getMessage(), e);
200        }
201    }
202
203    @GET
204    @Path("{bank}/{collection}/view")
205    public Object getCollectionView(@PathParam("bank") String bank, @PathParam("collection") String collection) {
206        return getTemplate("collection.ftl").arg("collection", collection).arg("bank", bank);
207    }
208
209    @GET
210    @Path("{bank}/{collection}/{type}/info")
211    public Object getPresetCollectionInfo(@PathParam("bank") String bank, @PathParam("collection") String collection,
212            @PathParam("type") String typeName) {
213        try {
214            return BankManager.getInfoFile(bank, collection, typeName);
215        } catch (IOException e) {
216            throw new ThemeBankException(e.getMessage(), e);
217        }
218    }
219
220    @GET
221    @Path("{bank}/{collection}/style/view")
222    public Object getStyleCollectionsView(@PathParam("bank") String bank, @PathParam("collection") String collection) {
223        return getStyleCollections(bank, collection, false);
224    }
225
226    @GET
227    @Path("{bank}/{collection}/skin/view")
228    public Object getSkinsCollectionsView(@PathParam("bank") String bank, @PathParam("collection") String collection) {
229        return getStyleCollections(bank, collection, true);
230    }
231
232    public Object getStyleCollections(String bank, String collection, Boolean skins_only) {
233        return getTemplate("styleCollection.ftl").arg("styles", getItemsInCollection(bank, collection, "style")).arg(
234                "skins", listSkinsInCollection(bank, collection)).arg("collection", collection).arg("bank", bank).arg(
235                "skins_only", skins_only);
236    }
237
238    @GET
239    @Produces("text/css")
240    @Path("{bank}/{collection}/style/{resource}")
241    public Response getStyle(@PathParam("bank") String bank, @PathParam("collection") String collection,
242            @PathParam("resource") String resource) {
243        File file;
244        try {
245            file = BankManager.getStyleFile(bank, collection, resource);
246        } catch (IOException e) {
247            throw new ThemeBankException(e.getMessage(), e);
248        }
249        return Response.ok().entity(Utils.streamFile(file)).lastModified(new Date(file.lastModified())).header(
250                "Cache-Control", "public").header("Server", SERVER_ID).build();
251    }
252
253    @GET
254    @Path("{bank}/{collection}/style/{resource}/{action}")
255    public Object renderStyle(@PathParam("bank") String bank, @PathParam("collection") String collection,
256            @PathParam("resource") String resource, @PathParam("action") String action) {
257        return getTemplate("style.ftl").arg("content", getStyleContent(bank, collection, resource)).arg("bank", bank).arg(
258                "resource", resource).arg("collection", collection).arg("action", action).arg("is_skin", true);
259    }
260
261    @GET
262    @Path("{bank}/{collection}/skin/{resource}/{action}")
263    public Object renderSkin(@PathParam("bank") String bank, @PathParam("collection") String collection,
264            @PathParam("resource") String resource, @PathParam("action") String action) {
265        return getTemplate("style.ftl").arg("content", getStyleContent(bank, collection, resource)).arg("bank", bank).arg(
266                "resource", resource).arg("collection", collection).arg("action", action).arg("is_skin", true);
267    }
268
269    @GET
270    @Path("{bank}/{collection}/style/{resource}/preview")
271    public Object displayStylePreview(@PathParam("bank") String bank, @PathParam("collection") String collection,
272            @PathParam("resource") String resource) {
273        File file;
274        try {
275            file = BankManager.getStylePreviewFile(bank, collection, resource);
276        } catch (IOException e) {
277            return noPreview();
278        }
279        String ext = FileUtils.getFileExtension(path);
280        String mimeType = ctx.getEngine().getMimeType(ext);
281        if (mimeType == null) {
282            mimeType = "application/octet-stream";
283        }
284        return Response.ok().entity(Utils.streamFile(file)).lastModified(new Date(file.lastModified())).header(
285                "Cache-Control", "public").header("Server", SERVER_ID).type(mimeType).build();
286    }
287
288    public String getStyleContent(String bank, String collection, String resource) {
289        File file;
290        try {
291            file = BankManager.getStyleFile(bank, collection, resource);
292        } catch (IOException e) {
293            throw new ThemeBankException(e.getMessage(), e);
294        }
295        String content;
296        try {
297            content = BankUtils.getFileContent(file);
298        } catch (IOException e) {
299            throw new ThemeBankException(e.getMessage(), e);
300        }
301        return content;
302    }
303
304    /*
305     * Presets
306     */
307
308    @GET
309    @Path("{bank}/{collection}/preset/view")
310    public Object getPresetCollectionView(@PathParam("bank") String bank, @PathParam("collection") String collection) {
311        return getTemplate("presetCollection.ftl").arg("presets", getItemsInCollection(bank, collection, "preset")).arg(
312                "collection", collection).arg("bank", bank);
313    }
314
315    @GET
316    @Produces(MediaType.TEXT_PLAIN)
317    @Path("{bank}/{collection}/preset/{category}")
318    public Response getPreset(@PathParam("bank") String bank, @PathParam("collection") String collection,
319            @PathParam("category") String category) {
320        String path = String.format("%s/%s/preset/%s", bank, collection, category);
321        File file;
322        try {
323            file = BankManager.getFile(path);
324        } catch (IOException e) {
325            throw new ThemeBankException(e.getMessage(), e);
326        }
327        String content = "";
328        if (!file.exists()) {
329            return Response.status(404).build();
330        }
331
332        StringBuilder sb = new StringBuilder();
333        for (File f : file.listFiles()) {
334            try {
335                content = BankUtils.getFileContent(f);
336            } catch (IOException e) {
337                log.warn("Could not read file: " + f.getAbsolutePath());
338                continue;
339            }
340            content = PaletteParser.renderPaletteAsCsv(content.getBytes(), f.getName());
341            sb.append(content);
342        }
343        content = sb.toString();
344        return Response.ok(content).lastModified(new Date(file.lastModified())).header("Cache-Control", "public").header(
345                "Server", SERVER_ID).build();
346    }
347
348    @GET
349    @Path("{bank}/{collection}/preset/{category}/view")
350    public Object getPresetView(@PathParam("bank") String bank, @PathParam("collection") String collection,
351            @PathParam("category") String category) {
352        Properties properties = Utils.getPresetProperties(bank, collection, category);
353        return getTemplate("preset.ftl").arg("properties", properties).arg("bank", bank).arg("collection", collection).arg(
354                "category", category);
355    }
356
357    /*
358     * Images
359     */
360
361    @GET
362    @Path("{bank}/{collection}/image/view")
363    public Object getImageCollectionView(@PathParam("bank") String bank, @PathParam("collection") String collection) {
364        return getTemplate("imageCollection.ftl").arg("images", getItemsInCollection(bank, collection, "image")).arg(
365                "collection", collection).arg("bank", bank);
366    }
367
368    @GET
369    @Path("{bank}/{collection}/image/{resource}")
370    public Response getImage(@PathParam("bank") String bank, @PathParam("collection") String collection,
371            @PathParam("resource") String resource) {
372        File file;
373        try {
374            file = BankManager.getImageFile(bank, collection, resource);
375        } catch (IOException e) {
376            throw new ThemeBankException(e.getMessage(), e);
377        }
378        String ext = FileUtils.getFileExtension(file.getPath());
379        String mimeType = ctx.getEngine().getMimeType(ext);
380        if (mimeType == null) {
381            mimeType = "application/octet-stream";
382        }
383        return Response.ok().entity(Utils.streamFile(file)).lastModified(new Date(file.lastModified())).header(
384                "Cache-Control", "public").header("Server", SERVER_ID).type(mimeType).build();
385    }
386
387    @GET
388    @Path("{bank}/{collection}/image/{resource}/view")
389    public Object getImageView(@PathParam("bank") String bank, @PathParam("collection") String collection,
390            @PathParam("resource") String resource) {
391        return getTemplate("image.ftl").arg("bank", bank).arg("resource", resource).arg("collection", collection);
392    }
393
394    @GET
395    @Produces(MediaType.APPLICATION_JSON)
396    @Path("{bank}/json/images")
397    public String listImages(@PathParam("bank") String bank) {
398        try {
399            return Utils.listImages(bank);
400        } catch (IOException e) {
401            throw new ThemeBankException(e.getMessage(), e);
402        }
403    }
404
405    @GET
406    @Produces(MediaType.APPLICATION_JSON)
407    @Path("{bank}/json/collections")
408    public String listCollections(@PathParam("bank") String bank) {
409        try {
410            return Utils.listCollections(bank);
411        } catch (IOException e) {
412            throw new ThemeBankException(e.getMessage(), e);
413        }
414    }
415
416    @GET
417    @Produces(MediaType.APPLICATION_JSON)
418    @Path("json/tree")
419    public String getTree() throws IOException {
420        return Utils.getNavTree();
421    }
422
423    private Object noPreview() {
424        return redirect(ctx.getModulePath() + "/skin/img/no-preview.png");
425    }
426
427    /*
428     * API
429     */
430    public static List<String> getBankNames() {
431        return BankManager.getBankNames();
432    }
433
434    public static List<String> getCollections(String bankName) {
435        try {
436            return Utils.getCollections(bankName);
437        } catch (IOException e) {
438            throw new ThemeBankException(e.getMessage(), e);
439        }
440    }
441
442    public List<String> listSkinsInCollection(String bankName, String collection) {
443        try {
444            return Utils.listSkinsInCollection(bankName, collection);
445        } catch (IOException e) {
446            throw new ThemeBankException(e.getMessage(), e);
447        }
448    }
449
450    public static List<String> getItemsInCollection(String bankName, String collection, String typeName) {
451        try {
452            return Utils.getItemsInCollection(bankName, collection, typeName);
453        } catch (IOException e) {
454            throw new ThemeBankException(e.getMessage(), e);
455        }
456    }
457
458    // handle errors
459    @Override
460    public Object handleError(WebApplicationException e) {
461        if (e instanceof WebSecurityException) {
462            return Response.status(401).entity(getTemplate("session.ftl").arg("redirect_url", ctx.getUrlPath())).type(
463                    "text/html").build();
464        } else if (e instanceof NotFoundException) {
465            return Response.status(404).entity(getTemplate("not_found.ftl")).type("text/html").build();
466        } else if (e instanceof WebException) {
467            return Response.status(500).entity(
468                    getTemplate("error.ftl").arg("stacktrace", ((WebException) e).getStackTraceString())).type(
469                    "text/html").build();
470        } else {
471
472            return super.handleError(e);
473        }
474    }
475
476}