001/*
002 * (C) Copyright 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 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.forms.layout.export;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026
027import javax.servlet.http.HttpServletRequest;
028import javax.ws.rs.GET;
029import javax.ws.rs.Path;
030import javax.ws.rs.PathParam;
031import javax.ws.rs.QueryParam;
032import javax.ws.rs.core.Context;
033import javax.ws.rs.core.Response;
034import javax.ws.rs.core.UriInfo;
035
036import org.apache.commons.lang.StringUtils;
037import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
038import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
039import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetTypeDefinitionComparator;
040import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
041import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException;
042import org.nuxeo.ecm.webengine.model.view.TemplateView;
043import org.nuxeo.runtime.api.Framework;
044
045/**
046 * Exports and presents documentation about widget type definitions
047 *
048 * @author Anahide Tchertchian
049 * @since 5.4
050 */
051public class WidgetTypeResource {
052
053    protected final String category;
054
055    protected LayoutStore service;
056
057    protected final List<WidgetTypeDefinition> widgetTypes;
058
059    protected final Map<String, List<WidgetTypeDefinition>> widgetTypesByCat;
060
061    public WidgetTypeResource(String category) {
062        this.category = category;
063        service = Framework.getService(LayoutStore.class);
064        widgetTypes = service.getWidgetTypeDefinitions(category);
065        // sort so that order is deterministic
066        Collections.sort(widgetTypes, new WidgetTypeDefinitionComparator(true));
067        widgetTypesByCat = getWidgetTypesByCategory();
068    }
069
070    protected Map<String, List<WidgetTypeDefinition>> getWidgetTypesByCategory() {
071        Map<String, List<WidgetTypeDefinition>> cats = new HashMap<String, List<WidgetTypeDefinition>>();
072        List<WidgetTypeDefinition> unknownCatWidgets = new ArrayList<WidgetTypeDefinition>();
073        for (WidgetTypeDefinition wTypeDef : widgetTypes) {
074            List<String> categories = null;
075            WidgetTypeConfiguration conf = wTypeDef.getConfiguration();
076            if (conf != null) {
077                categories = conf.getCategories();
078            }
079            boolean added = false;
080            if (categories != null) {
081                for (String cat : categories) {
082                    List<WidgetTypeDefinition> list = cats.get(cat);
083                    if (list == null) {
084                        list = new ArrayList<WidgetTypeDefinition>();
085                    }
086                    list.add(wTypeDef);
087                    cats.put(cat, list);
088                    added = true;
089                }
090            }
091            if (!added) {
092                unknownCatWidgets.add(wTypeDef);
093            }
094        }
095        if (!unknownCatWidgets.isEmpty()) {
096            cats.put("unknown", unknownCatWidgets);
097        }
098        // sort by category key
099        List<String> sortedKeys = new ArrayList<String>(cats.keySet());
100        Collections.sort(sortedKeys);
101        Map<String, List<WidgetTypeDefinition>> res = new LinkedHashMap<String, List<WidgetTypeDefinition>>();
102        for (String key : sortedKeys) {
103            res.put(key, cats.get(key));
104        }
105        return res;
106    }
107
108    /**
109     * Returns widget types definitions for given categories
110     * <p>
111     * If the category is null, the filter does not check the category. Widget types without a configuration are
112     * included if boolean 'all' is set to true. Mutliple categories are extracted from the query parameter by splitting
113     * on the space character.
114     * <p>
115     * If not null, the version parameter will exclude all widget types that did not exist before this version.
116     */
117    @GET
118    @Path("widgetTypes")
119    public Object getWidgetTypeDefinitions(@Context HttpServletRequest request,
120            @QueryParam("categories") String categories, @QueryParam("version") String version,
121            @QueryParam("all") Boolean all) {
122        // TODO: refactor so that's cached
123        List<String> catsList = new ArrayList<String>();
124        if (categories != null) {
125            for (String cat : categories.split(" ")) {
126                catsList.add(cat);
127            }
128        }
129        WidgetTypeDefinitions res = new WidgetTypeDefinitions();
130        for (WidgetTypeDefinition def : widgetTypes) {
131            WidgetTypeConfiguration conf = def.getConfiguration();
132            if (!Boolean.TRUE.equals(all) && conf == null) {
133                continue;
134            }
135            if (version != null && conf != null) {
136                String confVersion = conf.getSinceVersion();
137                if (isStriclyBeforeVersion(version, confVersion)) {
138                    continue;
139                }
140            }
141            if (catsList != null && !catsList.isEmpty()) {
142                boolean hasCats = false;
143                if (conf != null) {
144                    // filter on category
145                    List<String> confCats = conf.getCategories();
146                    if (confCats != null) {
147                        hasCats = true;
148                        if (confCats.containsAll(catsList)) {
149                            res.add(def);
150                        }
151                    }
152                }
153                if (!hasCats && catsList.size() == 1 && catsList.contains("unknown")) {
154                    res.add(def);
155                }
156            } else {
157                if (conf == null && !Boolean.TRUE.equals(all)) {
158                    continue;
159                }
160                res.add(def);
161            }
162        }
163        return res;
164    }
165
166    protected boolean isStriclyBeforeVersion(String ref, String version) {
167        if (version == null || version.trim().length() == 0) {
168            return true;
169        }
170
171        String[] components1 = ref.split("\\.");
172        String[] components2 = version.split("\\.");
173        int length = Math.min(components1.length, components2.length);
174        for(int i = 0; i < length; i++) {
175            int result = Integer.compare(Integer.valueOf(components1[i]), Integer.valueOf(components2[i]));
176            if (result != 0) {
177                return result < 0;
178            }
179        }
180        return components1.length < components2.length;
181    }
182
183    /**
184     * Returns widget types definitions for given category.
185     * <p>
186     * If the category is null, the filter does not check the category. Widget types without a configuration are
187     * included if boolean 'all' is set to true.
188     * <p>
189     * If not null, the version parameter will exclude all widget types that did not exist before this version.
190     */
191    @GET
192    @Path("widgetTypes/{category}")
193    public Object getWidgetTypeDefinitionsForCategory(@Context HttpServletRequest request,
194            @PathParam("category") String category, @QueryParam("version") String version,
195            @QueryParam("all") Boolean all) {
196        return getWidgetTypeDefinitions(request, category, version, all);
197    }
198
199    @GET
200    @Path("widgetType/{name}")
201    public Object getWidgetTypeDefinition(@Context HttpServletRequest request, @PathParam("name") String name) {
202        WidgetTypeDefinition def = service.getWidgetTypeDefinition(category, name);
203        if (def != null) {
204            return def;
205        } else {
206            return Response.status(401).build();
207        }
208    }
209
210    public TemplateView getTemplate(@Context UriInfo uriInfo) {
211        return getTemplate("widget-types.ftl", uriInfo);
212    }
213
214    @GET
215    @Path("wiki")
216    public Object getWikiDocumentation(@Context UriInfo uriInfo) {
217        return getTemplate("widget-types-wiki.ftl", uriInfo);
218    }
219
220    protected List<String> getNuxeoVersions() {
221        if ("jsf".equals(category) || "jsfAction".equals(category)) {
222            return Arrays.asList("5.8", "6.0", "7.10");
223        }
224        return Collections.emptyList();
225    }
226
227    protected TemplateView getTemplate(String name, UriInfo uriInfo) {
228        String baseURL = uriInfo.getAbsolutePath().toString();
229        if (!baseURL.endsWith("/")) {
230            baseURL += "/";
231        }
232        TemplateView tv = new TemplateView(this, name);
233        tv.arg("categories", widgetTypesByCat);
234        tv.arg("nuxeoVersions", getNuxeoVersions());
235        tv.arg("widgetTypeCategory", category);
236        tv.arg("widgetTypes", widgetTypes);
237        tv.arg("baseURL", baseURL);
238        return tv;
239    }
240
241    @GET
242    public Object doGet(@QueryParam("widgetType") String widgetTypeName, @Context UriInfo uriInfo) {
243        if (widgetTypeName == null) {
244            return getTemplate(uriInfo);
245        } else {
246            WidgetTypeDefinition wType = service.getWidgetTypeDefinition(category, widgetTypeName);
247            if (wType == null) {
248                throw new WebResourceNotFoundException("No widget type found with name: " + widgetTypeName);
249            }
250            TemplateView tpl = getTemplate(uriInfo);
251            tpl.arg("widgetType", wType);
252            return tpl;
253        }
254    }
255
256    public String getWidgetTypeLabel(WidgetTypeDefinition wTypeDef) {
257        if (wTypeDef != null) {
258            WidgetTypeConfiguration conf = wTypeDef.getConfiguration();
259            if (conf != null) {
260                return conf.getTitle();
261            }
262            return wTypeDef.getName();
263        }
264        return null;
265    }
266
267    public String getWidgetTypeDescription(WidgetTypeDefinition wTypeDef) {
268        if (wTypeDef != null) {
269            WidgetTypeConfiguration conf = wTypeDef.getConfiguration();
270            if (conf != null) {
271                return conf.getDescription();
272            }
273        }
274        return null;
275    }
276
277    public List<String> getWidgetTypeCategories(WidgetTypeDefinition wTypeDef) {
278        if (wTypeDef != null) {
279            WidgetTypeConfiguration conf = wTypeDef.getConfiguration();
280            if (conf != null) {
281                return conf.getCategories();
282            }
283        }
284        return null;
285    }
286
287    public String getWidgetTypeCategoriesAsString(WidgetTypeDefinition wTypeDef) {
288        List<String> categories = getWidgetTypeCategories(wTypeDef);
289        if (categories == null) {
290            return "";
291        } else {
292            return StringUtils.join(categories, ", ");
293        }
294    }
295
296}