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