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