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