001/* 002 * (C) Copyright 2007-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 * Nuxeo - initial API and implementation 018 * 019 * $Id: Functions.java 28572 2008-01-08 14:40:44Z fguillaume $ 020 */ 021 022package org.nuxeo.ecm.platform.ui.web.tag.fn; 023 024import java.text.DateFormat; 025import java.text.SimpleDateFormat; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036 037import javax.faces.component.UIComponent; 038import javax.faces.context.FacesContext; 039import javax.servlet.http.HttpServletRequest; 040 041import org.apache.commons.io.FilenameUtils; 042import org.apache.commons.lang.StringEscapeUtils; 043import org.apache.commons.lang.StringUtils; 044import org.nuxeo.common.utils.i18n.I18NUtils; 045import org.nuxeo.ecm.core.api.NuxeoGroup; 046import org.nuxeo.ecm.core.api.NuxeoPrincipal; 047import org.nuxeo.ecm.core.api.security.SecurityConstants; 048import org.nuxeo.ecm.platform.ui.web.rest.RestHelper; 049import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 050import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 051import org.nuxeo.ecm.platform.ui.web.util.ComponentRenderUtils; 052import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 053import org.nuxeo.ecm.platform.url.DocumentViewImpl; 054import org.nuxeo.ecm.platform.url.api.DocumentView; 055import org.nuxeo.ecm.platform.usermanager.UserManager; 056import org.nuxeo.runtime.api.Framework; 057import org.nuxeo.runtime.services.config.ConfigurationService; 058 059/** 060 * Util functions. 061 * 062 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 063 * @author <a href="mailto:tm@nuxeo.com">Thierry Martins</a> 064 */ 065public final class Functions { 066 067 public static final String I18N_DURATION_PREFIX = "label.duration.unit."; 068 069 public static final String BIG_FILE_SIZE_LIMIT_PROPERTY = "org.nuxeo.big.file.size.limit"; 070 071 public static final long DEFAULT_BIG_FILE_SIZE_LIMIT = 5 * 1024 * 1024; 072 073 /** 074 * @since 7.4 075 */ 076 public static final String BYTE_PREFIX_FORMAT_PROPERTY = "nuxeo.jsf.defaultBytePrefixFormat"; 077 078 /** 079 * @since 7.4 080 */ 081 public static final String DEFAULT_BYTE_PREFIX_FORMAT = "SI"; 082 083 public static final Pattern YEAR_PATTERN = Pattern.compile("y+"); 084 085 public enum BytePrefix { 086 087 SI(1000, new String[] { "", "k", "M", "G", "T", "P", "E", "Z", "Y" }, new String[] { "", "kilo", "mega", 088 "giga", "tera", "peta", "exa", "zetta", "yotta" }), IEC(1024, new String[] { "", "Ki", "Mi", "Gi", 089 "Ti", "Pi", "Ei", "Zi", "Yi" }, new String[] { "", "kibi", "mebi", "gibi", "tebi", "pebi", "exbi", 090 "zebi", "yobi" }), JEDEC(1024, new String[] { "", "K", "M", "G" }, new String[] { "", "kilo", "mega", 091 "giga" }); 092 093 private final int base; 094 095 private final String[] shortSuffixes; 096 097 private final String[] longSuffixes; 098 099 BytePrefix(int base, String[] shortSuffixes, String[] longSuffixes) { 100 this.base = base; 101 this.shortSuffixes = shortSuffixes; 102 this.longSuffixes = longSuffixes; 103 } 104 105 public int getBase() { 106 return base; 107 } 108 109 public String[] getShortSuffixes() { 110 return shortSuffixes; 111 } 112 113 public String[] getLongSuffixes() { 114 return longSuffixes; 115 } 116 117 } 118 119 // XXX we should not use a static variable for this cache, but use a cache 120 // at a higher level in the Framework or in a facade. 121 private static UserManager userManager; 122 123 /** 124 * @since 7.2 125 */ 126 private static UserNameResolverHelper userNameResolver = new UserNameResolverHelper(); 127 128 static final Map<String, String> mapOfDateLength = new HashMap<String, String>() { 129 { 130 put("short", String.valueOf(DateFormat.SHORT)); 131 put("shortWithCentury".toLowerCase(), "shortWithCentury".toLowerCase()); 132 put("medium", String.valueOf(DateFormat.MEDIUM)); 133 put("long", String.valueOf(DateFormat.LONG)); 134 put("full", String.valueOf(DateFormat.FULL)); 135 } 136 137 private static final long serialVersionUID = 8465772256977862352L; 138 }; 139 140 // Utility class. 141 private Functions() { 142 } 143 144 public static Object test(Boolean test, Object onSuccess, Object onFailure) { 145 return test ? onSuccess : onFailure; 146 } 147 148 public static String join(String[] list, String separator) { 149 return StringUtils.join(list, separator); 150 } 151 152 public static String joinCollection(Collection<Object> collection, String separator) { 153 if (collection == null) { 154 return null; 155 } 156 return StringUtils.join(collection.iterator(), separator); 157 } 158 159 public static String htmlEscape(String data) { 160 return StringEscapeUtils.escapeHtml(data); 161 } 162 163 /** 164 * Escapes a given string to be used in a JavaScript function (escaping single quote characters for instance). 165 * 166 * @since 5.4.2 167 */ 168 public static String javaScriptEscape(String data) { 169 if (data != null) { 170 data = StringEscapeUtils.escapeJavaScript(data); 171 } 172 return data; 173 } 174 175 /** 176 * Can be used in order to produce something like that "Julien, Alain , Thierry et Marc-Aurele" where ' , ' and ' et 177 * ' is the final one. 178 */ 179 public static String joinCollectionWithFinalDelimiter(Collection<Object> collection, String separator, 180 String finalSeparator) { 181 return joinArrayWithFinalDelimiter(collection.toArray(), separator, finalSeparator); 182 } 183 184 public static String joinArrayWithFinalDelimiter(Object[] collection, String separator, String finalSeparator) { 185 if (collection == null) { 186 return null; 187 } 188 StringBuffer result = new StringBuffer(); 189 result.append(StringUtils.join(collection, separator)); 190 if (collection != null && collection.length > 0 && !StringUtils.isBlank(finalSeparator)) { 191 result.append(finalSeparator); 192 } 193 return result.toString(); 194 } 195 196 public static String formatDateUsingBasicFormatter(Date date) { 197 return formatDate(date, basicDateFormatter()); 198 } 199 200 public static String formatDate(Date date, String format) { 201 return new SimpleDateFormat(format).format(date); 202 } 203 204 public static String concat(String s1, String s2) { 205 return s1 + s2; 206 } 207 208 public static String indentString(Integer level, String text) { 209 StringBuilder label = new StringBuilder(""); 210 for (int i = 0; i < level; i++) { 211 label.append(text); 212 } 213 return label.toString(); 214 } 215 216 public static boolean userIsMemberOf(String groupName) { 217 FacesContext context = FacesContext.getCurrentInstance(); 218 NuxeoPrincipal principal = (NuxeoPrincipal) context.getExternalContext().getUserPrincipal(); 219 return principal.isMemberOf(groupName); 220 } 221 222 private static UserManager getUserManager() { 223 if (userManager == null) { 224 userManager = Framework.getService(UserManager.class); 225 } 226 return userManager; 227 } 228 229 /** 230 * Returns the full name of a user, or its username if user if not found. 231 * <p> 232 * Since 5.5, returns null if given username is null (instead of returning the current user full name). 233 */ 234 public static String userFullName(String username) { 235 if (SecurityConstants.SYSTEM_USERNAME.equals(username)) { 236 // avoid costly and useless calls to the user directory 237 return username; 238 } 239 240 // empty user name is current user 241 if (StringUtils.isBlank(username)) { 242 return null; 243 } 244 return userNameResolver.getUserFullName(username); 245 } 246 247 /** 248 * Returns the full name of a group from his id 249 * 250 * @see #groupDisplayName(String, String) 251 * @param groupId the group id 252 * @return the group full name 253 * @since 5.5 254 */ 255 public static String groupFullName(String groupId) { 256 NuxeoGroup group = getUserManager().getGroup(groupId); 257 String groupLabel = group.getLabel(); 258 String groupName = group.getName(); 259 return groupDisplayName(groupName, groupLabel); 260 } 261 262 // this should be a method of the principal itself 263 public static String principalFullName(NuxeoPrincipal principal) { 264 String first = principal.getFirstName(); 265 String last = principal.getLastName(); 266 return userDisplayName(principal.getName(), first, last); 267 } 268 269 public static String userDisplayName(String id, String first, String last) { 270 if (first == null || first.length() == 0) { 271 if (last == null || last.length() == 0) { 272 return id; 273 } else { 274 return last; 275 } 276 } else { 277 if (last == null || last.length() == 0) { 278 return first; 279 } else { 280 return first + ' ' + last; 281 } 282 } 283 } 284 285 /** 286 * Return, from the id, the id its-self if neither last name nor name are found or the full name plus the email if 287 * this one exists 288 * 289 * @param id id of the user 290 * @param first first name of the user 291 * @param last last name of the user 292 * @param email email of the user 293 * @return id or full name with email if exists 294 * @since 5.5 295 */ 296 public static String userDisplayNameAndEmail(String id, String first, String last, String email) { 297 String userDisplayedName = userDisplayName(id, first, last); 298 if (userDisplayedName.equals(id)) { 299 return userDisplayedName; 300 } 301 if (email == null || email.length() == 0) { 302 return userDisplayedName; 303 } 304 return userDisplayedName + " " + email; 305 } 306 307 /** 308 * Choose between label or name the best string to display a group 309 * 310 * @param name the group name 311 * @param label the group name 312 * @return label if not empty or null, otherwise group name 313 * @since 5.5 314 */ 315 public static String groupDisplayName(String name, String label) { 316 return StringUtils.isBlank(label) ? name : label; 317 } 318 319 /** 320 * @deprecated since 5.9.1, use {@link #dateFormatter()} instead. 321 */ 322 @Deprecated 323 public static String dateFormater(String formatLength) { 324 return dateFormatter(formatLength); 325 } 326 327 /** 328 * Return the date format to handle date taking the user's locale into account. 329 * 330 * @since 5.9.1 331 */ 332 public static String dateFormatter(String formatLength) { 333 // A map to store temporary available date format 334 FacesContext context = FacesContext.getCurrentInstance(); 335 Locale locale = context.getViewRoot().getLocale(); 336 337 int style = DateFormat.SHORT; 338 String styleString = mapOfDateLength.get(formatLength.toLowerCase()); 339 boolean addCentury = false; 340 if ("shortWithCentury".toLowerCase().equals(styleString)) { 341 addCentury = true; 342 } else { 343 style = Integer.parseInt(styleString); 344 } 345 346 DateFormat aDateFormat = DateFormat.getDateInstance(style, locale); 347 348 // Cast to SimpleDateFormat to make "toPattern" method available 349 SimpleDateFormat format = (SimpleDateFormat) aDateFormat; 350 351 // return the date pattern 352 String pattern = format.toPattern(); 353 354 if (style == DateFormat.SHORT && addCentury) { 355 // hack to add century on generated pattern 356 pattern = YEAR_PATTERN.matcher(pattern).replaceAll("yyyy"); 357 } 358 return pattern; 359 } 360 361 /** 362 * @deprecated since 5.9.1, use {@link #basicDateFormatter()} instead. 363 */ 364 @Deprecated 365 public static String basicDateFormater() { 366 return basicDateFormatter(); 367 } 368 369 /** 370 * Return the date format to handle date taking the user's locale into account. Uses the pseudo "shortWithCentury" 371 * format. 372 * 373 * @since 5.9.1 374 */ 375 public static String basicDateFormatter() { 376 return dateFormatter("shortWithCentury"); 377 } 378 379 /** 380 * @deprecated since 5.9.1, use {@link #dateAndTimeFormatter(String)} instead. 381 */ 382 @Deprecated 383 public static String dateAndTimeFormater(String formatLength) { 384 return dateAndTimeFormatter(formatLength); 385 } 386 387 /** 388 * Return the date format to handle date and time taking the user's locale into account. 389 * 390 * @since 5.9.1 391 */ 392 public static String dateAndTimeFormatter(String formatLength) { 393 394 // A map to store temporary available date format 395 396 FacesContext context = FacesContext.getCurrentInstance(); 397 Locale locale = context.getViewRoot().getLocale(); 398 399 int style = DateFormat.SHORT; 400 String styleString = mapOfDateLength.get(formatLength.toLowerCase()); 401 boolean addCentury = false; 402 if ("shortWithCentury".toLowerCase().equals(styleString)) { 403 addCentury = true; 404 } else { 405 style = Integer.parseInt(styleString); 406 } 407 408 DateFormat aDateFormat = DateFormat.getDateTimeInstance(style, style, locale); 409 410 // Cast to SimpleDateFormat to make "toPattern" method available 411 SimpleDateFormat format = (SimpleDateFormat) aDateFormat; 412 413 // return the date pattern 414 String pattern = format.toPattern(); 415 416 if (style == DateFormat.SHORT && addCentury) { 417 // hack to add century on generated pattern 418 pattern = YEAR_PATTERN.matcher(pattern).replaceAll("yyyy"); 419 } 420 return pattern; 421 } 422 423 /** 424 * @deprecated since 5.9.1, use {@link #basicDateAndTimeFormatter()} instead. 425 */ 426 @Deprecated 427 public static String basicDateAndTimeFormater() { 428 return basicDateAndTimeFormatter(); 429 } 430 431 /** 432 * Return the date format to handle date and time taking the user's locale into account. Uses the pseudo 433 * "shortWithCentury" format. 434 * 435 * @since 5.9.1 436 */ 437 public static String basicDateAndTimeFormatter() { 438 return dateAndTimeFormatter("shortWithCentury"); 439 } 440 441 /** 442 * Returns the default byte prefix. 443 * 444 * @since 7.4 445 */ 446 public static BytePrefix getDefaultBytePrefix() { 447 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 448 return BytePrefix.valueOf( 449 configurationService.getProperty(BYTE_PREFIX_FORMAT_PROPERTY, DEFAULT_BYTE_PREFIX_FORMAT)); 450 } 451 452 public static String printFileSize(String size) { 453 return printFormatedFileSize(size, getDefaultBytePrefix().name(), true); 454 } 455 456 public static String printFormatedFileSize(String sizeS, String format, Boolean isShort) { 457 long size = (sizeS == null || "".equals(sizeS)) ? 0 : Long.parseLong(sizeS); 458 BytePrefix prefix = Enum.valueOf(BytePrefix.class, format); 459 int base = prefix.getBase(); 460 String[] suffix = isShort ? prefix.getShortSuffixes() : prefix.getLongSuffixes(); 461 int ex = 0; 462 while (size > base - 1 || ex > suffix.length) { 463 ex++; 464 size /= base; 465 } 466 467 FacesContext context = FacesContext.getCurrentInstance(); 468 String msg; 469 if (context != null) { 470 String bundleName = context.getApplication().getMessageBundle(); 471 Locale locale = context.getViewRoot().getLocale(); 472 msg = I18NUtils.getMessageString(bundleName, "label.bytes.suffix", null, locale); 473 if ("label.bytes.suffix".equals(msg)) { 474 // Set default value if no message entry found 475 msg = "B"; 476 } 477 } else { 478 // No faces context, set default value 479 msg = "B"; 480 } 481 482 return "" + size + " " + suffix[ex] + msg; 483 } 484 485 /** 486 * Format the duration of a media in a string of two consecutive units to best express the duration of a media, 487 * e.g.: 488 * <ul> 489 * <li>1 hr 42 min</li> 490 * <li>2 min 25 sec</li> 491 * <li>10 sec</li> 492 * <li>0 sec</li> 493 * </ul> 494 * 495 * @param durationObj a Float, Double, Integer, Long or String instance representing a duration in seconds 496 * @param i18nLabels a map to translate the days, hours, minutes and seconds labels 497 * @return the formatted string 498 */ 499 public static String printFormattedDuration(Object durationObj, Map<String, String> i18nLabels) { 500 501 if (i18nLabels == null) { 502 i18nLabels = new HashMap<String, String>(); 503 } 504 double duration = 0.0; 505 if (durationObj instanceof Float) { 506 duration = ((Float) durationObj).doubleValue(); 507 } else if (durationObj instanceof Double) { 508 duration = ((Double) durationObj).doubleValue(); 509 } else if (durationObj instanceof Integer) { 510 duration = ((Integer) durationObj).doubleValue(); 511 } else if (durationObj instanceof Long) { 512 duration = ((Long) durationObj).doubleValue(); 513 } else if (durationObj instanceof String) { 514 duration = Double.parseDouble((String) durationObj); 515 } 516 517 int days = (int) Math.floor(duration / (24 * 60 * 60)); 518 int hours = (int) Math.floor(duration / (60 * 60)) - days * 24; 519 int minutes = (int) Math.floor(duration / 60) - days * 24 * 60 - hours * 60; 520 int seconds = (int) Math.floor(duration) - days * 24 * 3600 - hours * 3600 - minutes * 60; 521 522 int[] components = { days, hours, minutes, seconds }; 523 String[] units = { "days", "hours", "minutes", "seconds" }; 524 String[] defaultLabels = { "d", "hr", "min", "sec" }; 525 526 String representation = null; 527 for (int i = 0; i < components.length; i++) { 528 if (components[i] != 0 || i == components.length - 1) { 529 String i18nLabel = i18nLabels.get(I18N_DURATION_PREFIX + units[i]); 530 if (i18nLabel == null) { 531 i18nLabel = defaultLabels[i]; 532 } 533 representation = String.format("%d %s", components[i], i18nLabel); 534 if (i < components.length - 1) { 535 i18nLabel = i18nLabels.get(I18N_DURATION_PREFIX + units[i + 1]); 536 if (i18nLabel == null) { 537 i18nLabel = defaultLabels[i + 1]; 538 } 539 representation += String.format(" %d %s", components[i + 1], i18nLabel); 540 } 541 break; 542 } 543 } 544 return representation; 545 } 546 547 public static String printFormattedDuration(Object durationObj) { 548 return printFormattedDuration(durationObj, null); 549 } 550 551 public static final String translate(String messageId, Object... params) { 552 return ComponentUtils.translate(FacesContext.getCurrentInstance(), messageId, params); 553 } 554 555 /** 556 * @return the big file size limit defined with the property org.nuxeo.big.file.size.limit 557 */ 558 public static long getBigFileSizeLimit() { 559 return getFileSize(Framework.getProperty(BIG_FILE_SIZE_LIMIT_PROPERTY, "")); 560 } 561 562 public static long getFileSize(String value) { 563 Pattern pattern = Pattern.compile("([1-9][0-9]*)([kmgi]*)", Pattern.CASE_INSENSITIVE); 564 Matcher m = pattern.matcher(value.trim()); 565 long number; 566 String multiplier; 567 if (!m.matches()) { 568 return DEFAULT_BIG_FILE_SIZE_LIMIT; 569 } 570 number = Long.valueOf(m.group(1)); 571 multiplier = m.group(2); 572 return getValueFromMultiplier(multiplier) * number; 573 } 574 575 /** 576 * Transform the parameter in entry according to these unit systems: 577 * <ul> 578 * <li>SI prefixes: k/M/G for kilo, mega, giga</li> 579 * <li>IEC prefixes: Ki/Mi/Gi for kibi, mebi, gibi</li> 580 * </ul> 581 * 582 * @param m : binary prefix multiplier 583 * @return the value of the multiplier as a long 584 */ 585 public static long getValueFromMultiplier(String m) { 586 if ("k".equalsIgnoreCase(m)) { 587 return 1L * 1000; 588 } else if ("Ki".equalsIgnoreCase(m)) { 589 return 1L << 10; 590 } else if ("M".equalsIgnoreCase(m)) { 591 return 1L * 1000 * 1000; 592 } else if ("Mi".equalsIgnoreCase(m)) { 593 return 1L << 20; 594 } else if ("G".equalsIgnoreCase(m)) { 595 return 1L * 1000 * 1000 * 1000; 596 } else if ("Gi".equalsIgnoreCase(m)) { 597 return 1L << 30; 598 } else { 599 return 1L; 600 } 601 } 602 603 /** 604 * Returns true if the faces context holds messages for given JSF component id, usually the form id. 605 * <p> 606 * Id given id is null, returns true if there is at least one client id with messages. 607 * <p> 608 * Since the form id might be prefixed with a container id in some cases, the method returns true if one of client 609 * ids with messages stats with given id, or if given id is contained in it. 610 * 611 * @since 5.4.2 612 */ 613 public static boolean hasMessages(String clientId) { 614 Iterator<String> it = FacesContext.getCurrentInstance().getClientIdsWithMessages(); 615 if (clientId == null) { 616 return it.hasNext(); 617 } else { 618 while (it.hasNext()) { 619 String id = it.next(); 620 if (id != null 621 && (id.startsWith(clientId + ":") || id.contains(":" + clientId + ":") || id.equals(clientId) || id.endsWith(":" 622 + clientId))) { 623 return true; 624 } 625 } 626 } 627 return false; 628 } 629 630 public static String userUrl(String patternName, String username, String viewId, boolean newConversation) { 631 return userUrl(patternName, username, viewId, newConversation, null); 632 } 633 634 public static String userUrl(String patternName, String username, String viewId, boolean newConversation, 635 HttpServletRequest req) { 636 Map<String, String> parameters = new HashMap<String, String>(); 637 parameters.put("username", username); 638 DocumentView docView = new DocumentViewImpl(null, viewId, parameters); 639 640 // generate url 641 URLPolicyService service = Framework.getService(URLPolicyService.class); 642 if (patternName == null || patternName.length() == 0) { 643 patternName = service.getDefaultPatternName(); 644 } 645 646 String baseURL = null; 647 if (req == null) { 648 baseURL = BaseURL.getBaseURL(); 649 } else { 650 baseURL = BaseURL.getBaseURL(req); 651 } 652 653 String url = service.getUrlFromDocumentView(patternName, docView, baseURL); 654 655 // pass conversation info if needed 656 if (!newConversation && url != null) { 657 url = RestHelper.addCurrentConversationParameters(url); 658 } 659 660 return url; 661 } 662 663 public static List<Object> combineLists(List<? extends Object>... lists) { 664 List<Object> combined = new ArrayList<Object>(); 665 for (List<? extends Object> list : lists) { 666 combined.addAll(list); 667 } 668 return combined; 669 } 670 671 /** 672 * Helper that escapes a string used as a JSF tag id: this is useful to replace characters that are not handled 673 * correctly in JSF context. 674 * <p> 675 * This method currently removes ASCII characters from the given string, and replaces "-" characters by "_" because 676 * the dash is an issue for forms rendered in ajax (see NXP-10793). 677 * <p> 678 * Also starting digits are replaced by the "_" character because a tag id cannot start with a digit. 679 * 680 * @since 5.7 681 * @return the escaped string 682 */ 683 public static String jsfTagIdEscape(String base) { 684 if (base == null) { 685 return null; 686 } 687 int n = base.length(); 688 StringBuilder res = new StringBuilder(); 689 for (int i = 0; i < n; i++) { 690 char c = base.charAt(i); 691 if (i == 0) { 692 if (!Character.isLetter(c) && (c != '_')) { 693 res.append("_"); 694 } else { 695 res.append(c); 696 } 697 } else { 698 if (!Character.isLetter(c) && !Character.isDigit(c) && (c != '_')) { 699 res.append("_"); 700 } else { 701 res.append(c); 702 } 703 } 704 } 705 return org.nuxeo.common.utils.StringUtils.toAscii(res.toString()); 706 } 707 708 /** 709 * Returns the extension from the given {@code filename}. 710 * <p> 711 * See {@link FilenameUtils#getExtension(String)}. 712 * 713 * @since 5.7 714 */ 715 public static String fileExtension(String filename) { 716 return FilenameUtils.getExtension(filename); 717 } 718 719 /** 720 * Returns the base name from the given {@code filename}. 721 * <p> 722 * See {@link FilenameUtils#getBaseName(String)}. 723 * 724 * @since 5.7 725 */ 726 public static String fileBaseName(String filename) { 727 return FilenameUtils.getBaseName(filename); 728 } 729 730 /** 731 * Joins two strings to get a valid render attribute for ajax components. 732 * 733 * @since 6.0 734 */ 735 public static String joinRender(String render1, String render2) { 736 if (StringUtils.isBlank(render1) && StringUtils.isBlank(render2)) { 737 return ""; 738 } 739 String res; 740 if (StringUtils.isBlank(render1)) { 741 res = render2; 742 } else if (StringUtils.isBlank(render2)) { 743 res = render1; 744 } else { 745 res = StringUtils.join(new String[] { render1, render2 }, " "); 746 res = res.replaceAll("\\s+", " "); 747 } 748 res = res.trim(); 749 return res; 750 } 751 752 /** 753 * Returns the target component absolute id given an anchor in the tree and a local id. 754 * <p> 755 * If given targetId parameter contains spaces, consider several ids should be resolved and split them. 756 * 757 * @since 6.0 758 * @param anchor the component anchor, used a localization for the target component in the tree. 759 * @param targetId the component to look for locally so as to return its absolute client id. 760 */ 761 public static String componentAbsoluteId(UIComponent anchor, String targetId) { 762 // handle case where several target ids could be given as input 763 if (targetId == null) { 764 return null; 765 } 766 if (targetId.contains(" ")) { 767 String res = ""; 768 for (String t : targetId.split(" ")) { 769 res = joinRender(res, ComponentRenderUtils.getComponentAbsoluteId(anchor, t.trim())); 770 } 771 return res; 772 } else { 773 return ComponentRenderUtils.getComponentAbsoluteId(anchor, targetId); 774 } 775 } 776}