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.webengine.WebException;
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).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 WebException.wrap("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    public String getCode(LinkDescriptor link, Resource resource) {
201        String parentPath;
202        if (adapter != ResourceType.ROOT_TYPE_NAME) {
203            parentPath = resource.getActiveAdapter().getPath();
204        } else {
205            parentPath = resource.getPath();
206        }
207        StringBuilder res = new StringBuilder();
208        res.append(parentPath);
209        // avoid adding duplicate '/' character
210        if (parentPath != null && parentPath.endsWith("/") && path != null && path.startsWith("/")) {
211            res.append(path.substring(1));
212        } else {
213            res.append(path);
214        }
215        return res.toString();
216    }
217
218    public boolean isFragment() {
219        return fragment != null;
220    }
221
222    public void applyFragment(LinkDescriptor fragment) {
223        if (fragment.categories != null && !fragment.categories.isEmpty()) {
224            if (categories == null) {
225                categories = new ArrayList<String>(fragment.categories);
226            } else {
227                categories.addAll(fragment.categories);
228            }
229        }
230        if (fragment.type != null && !fragment.type.equals(ResourceType.ROOT_TYPE_NAME)) {
231            type = fragment.type;
232        }
233        if (fragment.adapter != null && !fragment.adapter.equals(ResourceType.ROOT_TYPE_NAME)) {
234            adapter = fragment.adapter;
235        }
236        if (fragment.facets != null && fragment.facets.length > 0) {
237            if (facets == null) {
238                facets = fragment.facets;
239            } else {
240                Set<String> set = new HashSet<String>();
241                set.addAll(Arrays.asList(facets));
242                set.addAll(Arrays.asList(fragment.facets));
243                facets = set.toArray(new String[set.size()]);
244            }
245        }
246        if (fragment.handlerClass != null) {
247            handler = null;
248            handlerClass = fragment.handlerClass;
249        }
250        if (fragment.guard != null) {
251            guard = fragment.guard;
252        }
253        if (fragment.path != null) {
254            path = fragment.path;
255        }
256        this.fragment = fragment.fragment;
257    }
258
259    @Override
260    public LinkDescriptor clone() throws CloneNotSupportedException {
261        return (LinkDescriptor) super.clone();
262    }
263
264    @Override
265    public boolean equals(Object obj) {
266        if (obj == this) {
267            return true;
268        }
269        if (obj == null) {
270            return false;
271        }
272        if (obj instanceof LinkDescriptor) {
273            LinkDescriptor ld = (LinkDescriptor) obj;
274            return id.equals(ld.id) && Utils.streq(fragment, ld.fragment);
275        }
276        return false;
277    }
278
279    @Override
280    public String toString() {
281        return id;
282    }
283
284}