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 @Override 243 public int compareTo(Action anotherAction) { 244 int cmp = order - anotherAction.order; 245 if (cmp == 0) { 246 // make sure we have a deterministic sort 247 cmp = id.compareTo(anotherAction.id); 248 } 249 return cmp; 250 } 251 252 public List<String> getFilterIds() { 253 return filterIds; 254 } 255 256 public void setFilterIds(List<String> filterIds) { 257 this.filterIds = filterIds; 258 } 259 260 public ActionFilter[] getFilters() { 261 return filters; 262 } 263 264 public void setFilters(ActionFilter[] filters) { 265 this.filters = filters; 266 } 267 268 public void setCategories(String[] categories) { 269 this.categories = categories; 270 } 271 272 /** 273 * Returns the confirm javascript for this element. 274 * <p> 275 * Since 5.7.3, fallbacks on properties when link is not set and retrieve it using key "confirm". 276 */ 277 public String getConfirm() { 278 if (confirm == null) { 279 String conf = getStringProperty("confirm"); 280 if (conf == null) { 281 conf = ""; 282 } 283 return conf; 284 } else { 285 return confirm; 286 } 287 } 288 289 public void setConfirm(String confirm) { 290 this.confirm = confirm; 291 } 292 293 public boolean getAvailable() { 294 return available; 295 } 296 297 public void setAvailable(boolean available) { 298 this.available = available; 299 } 300 301 /** 302 * @since 8.2 303 */ 304 public boolean isFiltered() { 305 return filtered; 306 } 307 308 /** 309 * @since 8.2 310 */ 311 public void setFiltered(boolean filtered) { 312 this.filtered = filtered; 313 } 314 315 public String getHelp() { 316 if (help == null) { 317 String conf = getStringProperty("help"); 318 if (conf == null) { 319 conf = ""; 320 } 321 return conf; 322 } else { 323 return help; 324 } 325 } 326 327 public void setHelp(String title) { 328 help = title; 329 } 330 331 public boolean isImmediate() { 332 if (immediate == null) { 333 Map<String, Serializable> props = getProperties(); 334 if (props != null && props.containsKey("immediate")) { 335 Object obj = props.get("immediate"); 336 if (obj instanceof String) { 337 return Boolean.valueOf((String) obj).booleanValue(); 338 } else if (obj instanceof Boolean) { 339 return ((Boolean) obj).booleanValue(); 340 } 341 } 342 return false; 343 } 344 return immediate.booleanValue(); 345 } 346 347 public void setImmediate(boolean immediate) { 348 this.immediate = Boolean.valueOf(immediate); 349 } 350 351 /** 352 * @since 5.6 353 */ 354 public String getType() { 355 return type; 356 } 357 358 /** 359 * @since 5.6 360 */ 361 public void setType(String type) { 362 this.type = type; 363 } 364 365 /** 366 * @since 5.6 367 */ 368 public ActionPropertiesDescriptor getPropertiesDescriptor() { 369 return properties; 370 } 371 372 /** 373 * @since 5.6 374 */ 375 public void setPropertiesDescriptor(ActionPropertiesDescriptor properties) { 376 this.properties = properties; 377 this.propertiesCache = null; 378 } 379 380 /** 381 * Sets local properties programatically 382 * 383 * @since 5.6 384 */ 385 public void setProperties(Map<String, Serializable> localProperties) { 386 this.localProperties = localProperties; 387 this.propertiesCache = null; 388 } 389 390 /** 391 * Returns an aggregate of {@link #localProperties} and {@link #properties} set via descriptors. 392 * 393 * @since 5.6 394 */ 395 public Map<String, Serializable> getProperties() { 396 if (propertiesCache == null) { 397 propertiesCache = new HashMap<>(); 398 if (properties != null) { 399 propertiesCache.putAll(properties.getAllProperties()); 400 } 401 if (localProperties != null) { 402 propertiesCache.putAll(localProperties); 403 } 404 } 405 return propertiesCache; 406 } 407 408 /** 409 * @since 5.6 410 */ 411 public void setAccessKey(String accessKey) { 412 this.accessKey = accessKey; 413 } 414 415 /** 416 * @since 5.6 417 */ 418 public String getAccessKey() { 419 if (accessKey == null) { 420 return getStringProperty("accessKey"); 421 } 422 return accessKey; 423 } 424 425 @Override 426 public boolean equals(Object other) { 427 if (this == other) { 428 return true; 429 } 430 if (other == null) { 431 return false; 432 } 433 if (!(other instanceof Action)) { 434 return false; 435 } 436 Action otherAction = (Action) other; 437 return id == null ? otherAction.id == null : id.equals(otherAction.id); 438 } 439 440 @Override 441 public int hashCode() { 442 return id == null ? 0 : id.hashCode(); 443 } 444 445 @Override 446 public Action clone() { 447 Action clone = new Action(); 448 clone.id = id; 449 clone.link = link; 450 clone.enabled = enabled; 451 clone.label = label; 452 clone.icon = icon; 453 clone.confirm = confirm; 454 clone.help = help; 455 clone.immediate = immediate; 456 clone.accessKey = accessKey; 457 clone.type = type; 458 if (properties != null) { 459 clone.properties = properties.clone(); 460 } 461 clone.available = available; 462 clone.filtered = filtered; 463 clone.order = order; 464 if (categories != null) { 465 clone.categories = categories.clone(); 466 } 467 if (filterIds != null) { 468 clone.filterIds = new ArrayList<>(filterIds); 469 } 470 if (filters != null) { 471 clone.filters = new ActionFilter[filters.length]; 472 for (int i = 0; i < filters.length; i++) { 473 clone.filters[i] = filters[i].clone(); 474 } 475 } 476 return clone; 477 } 478 479}