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}