001/* 002 * (C) Copyright 2006-2016 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 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.ecm.platform.actions; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.nuxeo.common.xmap.annotation.XNode; 029import org.nuxeo.common.xmap.annotation.XNodeList; 030import org.nuxeo.common.xmap.annotation.XObject; 031import org.nuxeo.runtime.api.Framework; 032 033/** 034 * Descriptor for action. 035 * 036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 037 */ 038@XObject("action") 039public class Action implements Serializable, Cloneable, Comparable<Action> { 040 041 public static final String[] EMPTY_CATEGORIES = new String[0]; 042 043 private static final long serialVersionUID = 1L; 044 045 @XNode("@id") 046 protected String id = ""; 047 048 protected String link = null; 049 050 @XNode("@enabled") 051 protected Boolean enabled; 052 053 @XNode("@label") 054 protected String label; 055 056 @XNode("@icon") 057 protected String icon; 058 059 @XNode("@confirm") 060 protected String confirm; 061 062 @XNode("@help") 063 protected String help; 064 065 @XNode("@immediate") 066 protected Boolean immediate; 067 068 @XNode("@accessKey") 069 protected String accessKey; 070 071 /** 072 * @since 5.6 073 */ 074 @XNode("@type") 075 protected String type = null; 076 077 /** 078 * @since 5.6 079 */ 080 @XNode("properties") 081 protected ActionPropertiesDescriptor properties; 082 083 /** 084 * Extra set of properties to be used by API, when creating actions on the fly without contributions to the service. 085 * 086 * @since 5.6 087 */ 088 protected Map<String, Serializable> localProperties; 089 090 /** 091 * @since 5.6 092 */ 093 protected Map<String, Serializable> propertiesCache; 094 095 protected boolean available = true; 096 097 /** 098 * @since 8.2 099 */ 100 protected boolean filtered = false; 101 102 /** 103 * Attribute that provides a hint for action ordering. 104 * <p> 105 * :XXX: Action ordering remains a problem. We will continue to use the existing strategy of, by default, ordering 106 * actions by specificity of registration and order of definition. 107 */ 108 @XNode("@order") 109 protected int order = 0; 110 111 @XNodeList(value = "category", type = String[].class, componentType = String.class) 112 protected String[] categories = EMPTY_CATEGORIES; 113 114 // 'action -> filter(s)' association 115 116 @XNodeList(value = "filter-id", type = ArrayList.class, componentType = String.class) 117 protected List<String> filterIds; 118 119 @XNodeList(value = "filter", type = ActionFilter[].class, componentType = DefaultActionFilter.class) 120 protected ActionFilter[] filters; 121 122 public Action() { 123 } 124 125 public Action(String id, String[] categories) { 126 this.id = id; 127 this.categories = categories; 128 } 129 130 /** 131 * Returns true if the enabled element was set on the descriptor, useful for merging. 132 * 133 * @since 5.8 134 */ 135 public boolean isEnableSet() { 136 return enabled != null; 137 } 138 139 public boolean isEnabled() { 140 return enabled == null || Boolean.TRUE.equals(enabled); 141 } 142 143 public void setEnabled(boolean enabled) { 144 this.enabled = Boolean.valueOf(enabled); 145 } 146 147 protected String getStringProperty(String prop) { 148 Map<String, Serializable> props = getProperties(); 149 if (props != null && props.containsKey(prop)) { 150 Object obj = props.get(prop); 151 if (obj instanceof String) { 152 return (String) obj; 153 } 154 } 155 return null; 156 } 157 158 public String getLabel() { 159 if (label == null) { 160 return getStringProperty("label"); 161 } 162 return label; 163 } 164 165 public void setLabel(String label) { 166 this.label = label; 167 } 168 169 public String getIcon() { 170 if (icon == null) { 171 return getStringProperty("icon"); 172 } 173 return icon; 174 } 175 176 public void setIcon(String icon) { 177 this.icon = icon; 178 } 179 180 /** 181 * Returns the link for this action. 182 * <p> 183 * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "link". 184 */ 185 public String getLink() { 186 if (link == null) { 187 return getStringProperty("link"); 188 } 189 return link; 190 } 191 192 @XNode("@link") 193 public void setLink(String link) { 194 if (link != null) { 195 this.link = Framework.expandVars(link); 196 } 197 } 198 199 public String[] getCategories() { 200 return categories; 201 } 202 203 /** 204 * Returns the categories as a list. 205 * 206 * @since 7.2 207 */ 208 public List<String> getCategoryList() { 209 if (categories == null) { 210 return null; 211 } 212 return Arrays.asList(categories); 213 } 214 215 public String getId() { 216 return id; 217 } 218 219 @Override 220 public String toString() { 221 return id; 222 } 223 224 /** 225 * Returns the action order. 226 * 227 * @return the action order as an integer value 228 */ 229 public int getOrder() { 230 return order; 231 } 232 233 /** 234 * Sets the order of the action. 235 * 236 * @param order order of the action 237 */ 238 public void setOrder(int order) { 239 this.order = order; 240 } 241 242 public int compareTo(Action anotherAction) { 243 int cmp = order - anotherAction.order; 244 if (cmp == 0) { 245 // make sure we have a deterministic sort 246 cmp = id.compareTo(anotherAction.id); 247 } 248 return cmp; 249 } 250 251 public List<String> getFilterIds() { 252 return filterIds; 253 } 254 255 public void setFilterIds(List<String> filterIds) { 256 this.filterIds = filterIds; 257 } 258 259 public ActionFilter[] getFilters() { 260 return filters; 261 } 262 263 public void setFilters(ActionFilter[] filters) { 264 this.filters = filters; 265 } 266 267 public void setCategories(String[] categories) { 268 this.categories = categories; 269 } 270 271 /** 272 * Returns the confirm javascript for this element. 273 * <p> 274 * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm". 275 */ 276 public String getConfirm() { 277 if (confirm == null) { 278 String conf = getStringProperty("confirm"); 279 if (conf == null) { 280 conf = ""; 281 } 282 return conf; 283 } else { 284 return confirm; 285 } 286 } 287 288 public void setConfirm(String confirm) { 289 this.confirm = confirm; 290 } 291 292 public boolean getAvailable() { 293 return available; 294 } 295 296 public void setAvailable(boolean available) { 297 this.available = available; 298 } 299 300 /** 301 * @since 8.2 302 */ 303 public boolean isFiltered() { 304 return filtered; 305 } 306 307 /** 308 * @since 8.2 309 */ 310 public void setFiltered(boolean filtered) { 311 this.filtered = filtered; 312 } 313 314 public String getHelp() { 315 if (help == null) { 316 String conf = getStringProperty("help"); 317 if (conf == null) { 318 conf = ""; 319 } 320 return conf; 321 } else { 322 return help; 323 } 324 } 325 326 public void setHelp(String title) { 327 help = title; 328 } 329 330 public boolean isImmediate() { 331 if (immediate == null) { 332 Map<String, Serializable> props = getProperties(); 333 if (props != null && props.containsKey("immediate")) { 334 Object obj = props.get("immediate"); 335 if (obj instanceof String) { 336 return Boolean.valueOf((String) obj).booleanValue(); 337 } else if (obj instanceof Boolean) { 338 return ((Boolean) obj).booleanValue(); 339 } 340 } 341 return false; 342 } 343 return immediate.booleanValue(); 344 } 345 346 public void setImmediate(boolean immediate) { 347 this.immediate = Boolean.valueOf(immediate); 348 } 349 350 /** 351 * @since 5.6 352 */ 353 public String getType() { 354 return type; 355 } 356 357 /** 358 * @since 5.6 359 */ 360 public void setType(String type) { 361 this.type = type; 362 } 363 364 /** 365 * @since 5.6 366 */ 367 public ActionPropertiesDescriptor getPropertiesDescriptor() { 368 return properties; 369 } 370 371 /** 372 * @since 5.6 373 */ 374 public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) { 375 this.properties = properties; 376 this.propertiesCache = null; 377 } 378 379 /** 380 * Sets local properties programatically 381 * 382 * @since 5.6 383 */ 384 public void setProperties(Map<String, Serializable> localProperties) { 385 this.localProperties = localProperties; 386 this.propertiesCache = null; 387 } 388 389 /** 390 * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors. 391 * 392 * @since 5.6 393 */ 394 public Map<String, Serializable> getProperties() { 395 if (propertiesCache == null) { 396 propertiesCache = new HashMap<>(); 397 if (properties != null) { 398 propertiesCache.putAll(properties.getAllProperties()); 399 } 400 if (localProperties != null) { 401 propertiesCache.putAll(localProperties); 402 } 403 } 404 return propertiesCache; 405 } 406 407 /** 408 * @since 5.6 409 */ 410 public void setAccessKey(String accessKey) { 411 this.accessKey = accessKey; 412 } 413 414 /** 415 * @since 5.6 416 */ 417 public String getAccessKey() { 418 if (accessKey == null) { 419 return getStringProperty("accessKey"); 420 } 421 return accessKey; 422 } 423 424 @Override 425 public boolean equals(Object other) { 426 if (this == other) { 427 return true; 428 } 429 if (other == null) { 430 return false; 431 } 432 if (!(other instanceof Action)) { 433 return false; 434 } 435 Action otherAction = (Action) other; 436 return id == null ? otherAction.id == null : id.equals(otherAction.id); 437 } 438 439 @Override 440 public int hashCode() { 441 return id == null ? 0 : id.hashCode(); 442 } 443 444 @Override 445 public Action clone() { 446 Action clone = new Action(); 447 clone.id = id; 448 clone.link = link; 449 clone.enabled = enabled; 450 clone.label = label; 451 clone.icon = icon; 452 clone.confirm = confirm; 453 clone.help = help; 454 clone.immediate = immediate; 455 clone.accessKey = accessKey; 456 clone.type = type; 457 if (properties != null) { 458 clone.properties = properties.clone(); 459 } 460 clone.available = available; 461 clone.filtered = filtered; 462 clone.order = order; 463 if (categories != null) { 464 clone.categories = categories.clone(); 465 } 466 if (filterIds != null) { 467 clone.filterIds = new ArrayList<>(filterIds); 468 } 469 if (filters != null) { 470 clone.filters = new ActionFilter[filters.length]; 471 for (int i = 0; i < filters.length; i++) { 472 clone.filters[i] = filters[i].clone(); 473 } 474 } 475 return clone; 476 } 477 478}