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}