001/*
002 * (C) Copyright 2006-2008 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 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.webengine.model;
021
022import java.text.ParseException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029
030import org.nuxeo.common.xmap.annotation.XNode;
031import org.nuxeo.common.xmap.annotation.XNodeList;
032import org.nuxeo.common.xmap.annotation.XObject;
033import org.nuxeo.ecm.webengine.WebException;
034import org.nuxeo.ecm.webengine.security.Guard;
035import org.nuxeo.ecm.webengine.security.PermissionService;
036
037/**
038 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
039 */
040@XObject("link")
041public class LinkDescriptor implements Cloneable, LinkHandler {
042
043    @XNode("@id")
044    protected String id;
045
046    @XNode("@path")
047    protected String path;
048
049    @XNode("@fragment")
050    protected String fragment;
051
052    protected volatile LinkHandler handler;
053
054    @XNode("@handler")
055    protected String handlerClass;
056
057    @XNodeList(value = "category", type = ArrayList.class, componentType = String.class, nullByDefault = false)
058    protected List<String> categories;
059
060    @XNode(value = "type")
061    protected String type = ResourceType.ROOT_TYPE_NAME;
062
063    /**
064     * The object adapter the link may have as owner
065     */
066    @XNode(value = "adapter")
067    protected String adapter = ResourceType.ROOT_TYPE_NAME;
068
069    @XNodeList(value = "facet", type = String[].class, componentType = String.class, nullByDefault = true)
070    protected String[] facets;
071
072    protected Guard guard = Guard.DEFAULT;
073
074    public LinkDescriptor() {
075    }
076
077    public LinkDescriptor(String id) {
078        this(id, null);
079    }
080
081    public LinkDescriptor(String id, String fragment) {
082        this.id = id;
083        this.fragment = fragment;
084    }
085
086    @XNode("guard")
087    public void setGuard(String expr) throws ParseException {
088        guard = PermissionService.parse(expr);
089    }
090
091    public String getId() {
092        return id;
093    }
094
095    public String getPath() {
096        return path;
097    }
098
099    public void setHandler(LinkHandler handler) {
100        this.handler = handler;
101    }
102
103    public String getCode(Resource resource) {
104        try {
105            if (handler == null) {
106                if (handlerClass != null) {
107                    Object obj = resource.getModule().loadClass(handlerClass).newInstance();
108                    if (obj instanceof LinkHandlerFactory) {
109                        handler = ((LinkHandlerFactory) obj).getHandler(this, resource);
110                    } else {
111                        handler = (LinkHandler) obj;
112                    }
113                } else {
114                    handler = this;
115                }
116            }
117            return handler.getCode(this, resource);
118        } catch (ReflectiveOperationException e) {
119            throw WebException.wrap("Failed to instantiate link handler", e);
120        }
121    }
122
123    public LinkHandler getHandler() {
124        return handler;
125    }
126
127    public String getAdapter() {
128        return adapter;
129    }
130
131    public String getType() {
132        return type;
133    }
134
135    public String[] getFacets() {
136        return facets;
137    }
138
139    public void setCategories(List<String> categories) {
140        this.categories = categories;
141    }
142
143    public void addCategories(Collection<String> categories) {
144        this.categories.addAll(categories);
145    }
146
147    public void addCategory(String category) {
148        categories.add(category);
149    }
150
151    public List<String> getCategories() {
152        return categories;
153    }
154
155    public boolean hasCategory(String category) {
156        return categories != null && categories.contains(category);
157    }
158
159    public boolean acceptResource(Resource context) {
160        if (type == ResourceType.ROOT_TYPE_NAME && adapter == ResourceType.ROOT_TYPE_NAME && facets == null) {
161            return true;
162        }
163        if (facets != null && facets.length > 0) {
164            for (String facet : facets) {
165                if (!context.hasFacet(facet)) {
166                    return false;
167                }
168            }
169        }
170        if (type != ResourceType.ROOT_TYPE_NAME) {
171            if (adapter != ResourceType.ROOT_TYPE_NAME) {
172                if (!context.isInstanceOf(type)) {
173                    return false;
174                }
175            } else {
176                return context.isInstanceOf(type);
177            }
178        }
179        if (adapter != ResourceType.ROOT_TYPE_NAME) {
180            Resource adapterRs = context.getNext();
181            if (adapterRs != null && adapterRs.isAdapter()) {
182                return adapterRs.isInstanceOf(adapter);
183            }
184            return false;
185        }
186        return true;
187    }
188
189    public boolean isEnabled(Resource context) {
190        if (acceptResource(context)) {
191            return guard == null || guard.check(context);
192        }
193        return false;
194    }
195
196    // TODO: here we are using absolute paths -> will be better to use relative
197    // paths?
198    public String getCode(LinkDescriptor link, Resource resource) {
199        String parentPath;
200        if (adapter != ResourceType.ROOT_TYPE_NAME) {
201            parentPath = resource.getActiveAdapter().getPath();
202        } else {
203            parentPath = resource.getPath();
204        }
205        StringBuilder res = new StringBuilder();
206        res.append(parentPath);
207        // avoid adding duplicate '/' character
208        if (parentPath != null && parentPath.endsWith("/") && path != null && path.startsWith("/")) {
209            res.append(path.substring(1));
210        } else {
211            res.append(path);
212        }
213        return res.toString();
214    }
215
216    public boolean isFragment() {
217        return fragment != null;
218    }
219
220    public void applyFragment(LinkDescriptor fragment) {
221        if (fragment.categories != null && !fragment.categories.isEmpty()) {
222            if (categories == null) {
223                categories = new ArrayList<String>(fragment.categories);
224            } else {
225                categories.addAll(fragment.categories);
226            }
227        }
228        if (fragment.type != null && !fragment.type.equals(ResourceType.ROOT_TYPE_NAME)) {
229            type = fragment.type;
230        }
231        if (fragment.adapter != null && !fragment.adapter.equals(ResourceType.ROOT_TYPE_NAME)) {
232            adapter = fragment.adapter;
233        }
234        if (fragment.facets != null && fragment.facets.length > 0) {
235            if (facets == null) {
236                facets = fragment.facets;
237            } else {
238                Set<String> set = new HashSet<String>();
239                set.addAll(Arrays.asList(facets));
240                set.addAll(Arrays.asList(fragment.facets));
241                facets = set.toArray(new String[set.size()]);
242            }
243        }
244        if (fragment.handlerClass != null) {
245            handler = null;
246            handlerClass = fragment.handlerClass;
247        }
248        if (fragment.guard != null) {
249            guard = fragment.guard;
250        }
251        if (fragment.path != null) {
252            path = fragment.path;
253        }
254        this.fragment = fragment.fragment;
255    }
256
257    @Override
258    public LinkDescriptor clone() throws CloneNotSupportedException {
259        return (LinkDescriptor) super.clone();
260    }
261
262    @Override
263    public boolean equals(Object obj) {
264        if (obj == this) {
265            return true;
266        }
267        if (obj == null) {
268            return false;
269        }
270        if (obj instanceof LinkDescriptor) {
271            LinkDescriptor ld = (LinkDescriptor) obj;
272            return id.equals(ld.id) && Utils.streq(fragment, ld.fragment);
273        }
274        return false;
275    }
276
277    @Override
278    public String toString() {
279        return id;
280    }
281
282}