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}