All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.util.StructureUtil Maven / Gradle / Ivy

There is a newer version: 17.25.3
Show newest version
package com.almworks.jira.structure.util;

import com.almworks.integers.*;
import com.almworks.jira.structure.api.*;
import com.almworks.jira.structure.api2g.attribute.*;
import com.almworks.jira.structure.api2g.forest.*;
import com.almworks.jira.structure.api2g.item.ItemIdentity;
import com.almworks.jira.structure.api2g.row.StructureRow;
import com.almworks.jira.structure.api2g.structure.Structure;
import com.almworks.jira.structure.api2g.structure.StructureManager;
import com.atlassian.crowd.embedded.api.Group;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.crowd.embedded.impl.IdentifierUtils;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.issue.*;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.user.ApplicationUsers;
import com.atlassian.jira.util.I18nHelper;
import com.opensymphony.workflow.loader.ActionDescriptor;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.TypeReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

import static org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 

Contains miscellaneous utility methods.

*/ public class StructureUtil { private static final org.slf4j.Logger logger = LoggerFactory.getLogger(StructureUtil.class); public static final int MAX_PROPERTY_LENGTH = 250; public static final La STRING_LONG = new La() { public Long la(String argument) { try { return argument == null ? null : Long.parseLong(argument.trim()); } catch (NumberFormatException e) { return null; } } }; public static final La INT_ITERATOR_TO_INT = new La() { @Override public Integer la(IntIterator intIterator) { return intIterator.value(); } }; public static final La LONG_ITERATOR_TO_LONG = new La() { @Override public Long la(LongIterator longIterator) { return longIterator.value(); } }; public static final TypeReference> JSON_MAP = new TypeReference>() {}; public static PermissionLevel applyPermissions(List permissions, User user, List stack, La> resolver, PermissionLevel pass) { PermissionLevel r = pass; if (permissions != null) { for (PermissionRule permission : permissions) { r = permission.apply(user, r, stack, resolver); } } return r; } public static JAXBContext createJAXBContext(Class contextClass) { Thread thread = Thread.currentThread(); ClassLoader contextLoader = thread.getContextClassLoader(); ClassLoader[] loaders = {ClassLoader.getSystemClassLoader(), contextLoader, JAXBContext.class.getClassLoader(), contextClass.getClassLoader()}; JAXBContext context = null; try { context = JAXBContext.newInstance(contextClass); } catch (JAXBException e) { for (int i = 1; i < loaders.length; i++) { try { thread.setContextClassLoader(loaders[i]); context = JAXBContext.newInstance(contextClass); if (context != null) break; } catch (JAXBException e2) { // ignore } } if (context == null) { logger.error("cannot initialize JAXB context for " + contextClass + " (tried loaders: " + Arrays.asList(loaders) + ")", e); } } finally { thread.setContextClassLoader(contextLoader); } return context; } @Nullable public static Long getSingleParameterLong(@Nullable Map map, @Nullable String name) { if (map == null || name == null) return null; Object r = map.get(name); if (r instanceof Long || r instanceof Integer) { return ((Number) r).longValue(); } String value = getSingleString(r); if (value != null && value.length() > 0) { try { return Long.parseLong(value); } catch (NumberFormatException e) { logger.warn("bad parameter " + name + " [" + value + "]"); } } return null; } @Nullable public static Integer getSingleParameterInteger(@Nullable Map map, @Nullable String name) { if (map == null || name == null) return null; Object r = map.get(name); if (r instanceof Integer) { return (Integer) r; } String value = getSingleString(r); if (value != null && value.length() > 0) { value = value.trim(); try { return Integer.parseInt(value); } catch (NumberFormatException e) { logger.warn("bad parameter " + name + " [" + value + "]"); } } return null; } public static boolean getSingleParameterBoolean(@Nullable Map map, @Nullable String name) { if (map == null || name == null) return false; Object r = map.get(name); if (r instanceof Boolean) { return (Boolean) r; } String value = getSingleString(r); return value != null && "true".equalsIgnoreCase(value.trim()); } @Nullable public static String getSingleParameter(@Nullable Map map, @Nullable String name) { if (map == null || name == null) return null; return getSingleString(map.get(name)); } @Nullable private static String getSingleString(Object r) { if (r instanceof Collection) { Collection c = (Collection) r; r = c.isEmpty() ? null : c.iterator().next(); } else if (r instanceof String[]) { r = ((String[]) r).length == 0 ? null : ((String[]) r)[0]; } return r == null ? null : String.valueOf(r); } @NotNull public static List getMultiParameter(@Nullable Map map, @Nullable String name) { if (map == null || name == null) return Collections.emptyList(); Object r = map.get(name); if (r instanceof Collection) { Collection c = (Collection) r; ArrayList result = new ArrayList(c.size()); for (Object o : c) { if (o != null) result.add(String.valueOf(o)); } return result; } else if (r instanceof String[]) { String[] array = (String[]) r; return array.length == 0 ? Collections.emptyList() : Arrays.asList(array); } return r == null ? Collections.emptyList() : Collections.singletonList(String.valueOf(r)); } @NotNull public static List getMultiParameterLong(@Nullable Map map, @Nullable String name) { return STRING_LONG.arrayList(getMultiParameter(map, name)); } /** * Sets a possibly long value (template) as an application property. Because properties may be limited to 255 * characters, this method breaks up long property into several properties each no longer than allowed. * * @param properties properties interface * @param name the name of the property * @param value the value */ public static void setLongProperty(ApplicationProperties properties, String name, String value) { if (value == null) value = ""; int len = value.length(); int chunk = Math.min(len, MAX_PROPERTY_LENGTH); properties.setString(name, value.substring(0, chunk)); value = value.substring(chunk); len -= chunk; int step = 1; while (len > 0) { chunk = Math.min(len, MAX_PROPERTY_LENGTH); properties.setString(name + "." + (step++), value.substring(0, chunk)); value = value.substring(chunk); len -= chunk; } while (true) { String n = name + "." + (step++); String s = properties.getDefaultBackedString(n); if (s == null || s.length() <= 0) { break; } properties.setString(n, null); } } /** * Reads a value previously stored in properties using setLongValue * * @param properties application propeties * @param name the name of the property to read * @return property value or an empty string */ @NotNull public static String getLongProperty(ApplicationProperties properties, String name) { StringBuilder r = new StringBuilder(); String s = properties.getDefaultBackedString(name); int step = 1; while (s != null && s.length() > 0) { r.append(s); s = properties.getDefaultBackedString(name + "." + (step++)); } return r.toString(); } /** * Returns the last element of the given list if it's not empty, 0 otherwise. * * @param list the list of longs * @return last value of list or 0 if it's empty. */ public static long lastOrZero(LongList list) { return (list == null || list.isEmpty()) ? 0 : list.get(list.size() - 1); } /** * Tests whether the given lists are equal (contain the same numbers in the same order). * An empty list is considered equal to a null list. * * @param list1 the first list * @param list2 the second list * @return true if the lists are equal, false otherwise. */ public static boolean listsEqual(LongList list1, LongList list2) { boolean empty1 = list1 == null || list1.isEmpty(); boolean empty2 = list2 == null || list2.isEmpty(); if (empty1 && empty2) return true; if (empty1 || empty2) return false; return list1.equals(list2); } @Nullable public static List copyPermissionsOrNull(@Nullable Collection permissions) { return permissions == null ? null : copyPermissions(permissions); } @NotNull public static List copyPermissions(@Nullable Collection permissions) { ArrayList r = new ArrayList(permissions == null ? 0 : permissions.size()); if (permissions != null) { for (PermissionRule permission : permissions) { r.add(permission.clone()); } } return r; } /** * Returns a string representation of the issue that is used to write log messages. Writes issue ID and, if possible, * issue key. * @param issue the ID of the issue * @return string that can be used in output */ @NotNull public static String getDebugIssueString(@Nullable Long issue) { return issue == null ? "null" : appendDebugIssueString(issue, new StringBuilder()).toString(); } public static StringBuilder appendDebugIssueString(@Nullable Long issue, StringBuilder sb) { if (issue == null) sb.append("null"); else appendDebugIssueString(issue, getDebugIssueKey(issue), sb); return sb; } /** Returns issue key if possible. Use for debug output. */ @Nullable public static String getDebugIssueKey(long issueId) { try { IssueManager issueManager = ComponentAccessor.getIssueManager(); MutableIssue io = issueManager == null ? null : issueManager.getIssueObject(issueId); return io == null ? null : io.getKey(); } catch (Exception|LinkageError ignored) {} return null; } /** * Returns a string representation of the issue that is used to write log messages. Writes issue ID and, if possible, * issue key. * @param issue the issue * @return string that can be used in output */ @NotNull public static String getDebugIssueString(@Nullable Issue issue) { return issue == null ? "null" : appendDebugIssueString(issue.getId(), issue.getKey(), new StringBuilder()).toString(); } /** * @return issue key if the issue object is not {@code null}. Otherwise, if issue ID is not {@code null}, returns issue ID in the form "issue #123". Otherwise, * returns string "(no issue ID)". * Better suited for high-level log messages than {@link #getDebugIssueString(java.lang.Long)} in that it is easily recognizable as issue representation to JIRA admin. * */ public static String issueKeyOrId(@Nullable Issue issue, @Nullable Long issueId) { return issue != null ? issue.getKey() : issueId != null ? "issue #" + issueId : "(no issue ID)"; } @NotNull public static StringBuilder appendDebugIssueString(@Nullable Long issueId, @Nullable String issueKey, StringBuilder r) { if (issueKey != null) { r.append('(').append(issueKey).append(' ').append(issueId).append(')'); } else { r.append(issueId); } return r; } @NotNull public static StringBuilder appendDebugStructureString(final long structureId, final StructureManager manager, final StringBuilder sb) { return StructureAuth.sudo(new SimpleCallable() { @Override public StringBuilder call() { try { Structure structure = manager.getStructure(structureId, PermissionLevel.NONE); String name = structure.getName(); return sb.append('\'').append(name).append("' (#").append(structureId).append(')'); } catch (StructureException ignored) { return sb.append("#").append(structureId); } } }); } public static String getDebugStructureString(long structureId, StructureManager structureManager) { return appendDebugStructureString(structureId, structureManager, new StringBuilder()).toString(); } @NotNull public static String username(@Nullable User user) {//j6 ok String username = user != null ? user.getName() : "anonymous"; //j6 ok return username == null ? "user[name = null]" : username; } @NotNull public static String username(@Nullable ApplicationUser user) {//j6 ok String username = user != null ? user.getName() : "anonymous"; //j6 ok return username == null ? "user[name = null]" : username; } public static String debugConstant(IssueConstant constant) { return constant == null ? "" : constant.getName() + "(" + constant.getId() + ")"; } public static String debugAction(ActionDescriptor action) { return action == null ? "" : action.getName() + "(" + action.getId() + ")"; } public static String getTextInCurrentUserLocale(String key, Object... parameters) { return getText2(null, StructureAuth.getUser(), key, parameters); } @Deprecated public static String getText(@Nullable Locale locale, @Nullable User user, String textKey, Object... messageParameters) { return getText2(locale, ApplicationUsers.from(user), textKey, messageParameters); } /** * Formats the text taken from the i18n bundle with the specified parameters in the locale that is selected as follows: *
    *
  1. If locale is not null, it is used.
  2. *
  3. Otherwise, if user is not null, the user locale (set up in the user preferences) is used.
  4. *
  5. If both locale and user are null, JIRA default locale is used. In a highly unlikely * event when JIRA application parameters are inaccessible, {@link java.util.Locale#ROOT root} (neutral) locale is used.
  6. *
* @param messageParameters can be the same as {@link com.atlassian.jira.util.I18nHelper#getText(String, Object)} * */ @NotNull public static String getText2(@Nullable Locale locale, @Nullable ApplicationUser user, String textKey, Object... messageParameters) { I18nHelper.BeanFactory factory = ComponentAccessor.getI18nHelperFactory(); if (factory == null) return textKey + ' ' + Arrays.toString(messageParameters); I18nHelper i18nHelper = locale != null || user == null ? factory.getInstance(locale != null ? locale : getJiraDefaultLocale()) : factory.getInstance(user); return i18nHelper.getText(textKey, messageParameters); } public static Locale getJiraDefaultLocale() { ApplicationProperties appProps = ComponentAccessor.getApplicationProperties(); if (appProps == null) { logger.warn("No application properties: probably a System 2 plugin now? Using root (neutral) locale."); return Locale.ROOT; } Locale defaultLocale = appProps.getDefaultLocale(); if (defaultLocale == null) { logger.warn("No JIRA default locale. Using root (neutral) locale."); return Locale.ROOT; } return defaultLocale; } @Nullable public static String getUserKey(@Nullable User user) { ApplicationUser applicationUser = ApplicationUsers.from(user); return applicationUser == null ? null : applicationUser.getKey(); } public static String getUserKey(@Nullable ApplicationUser user) { return user == null ? null : user.getKey(); } @Nullable public static String getUserName(@Nullable User user) { return user == null ? null : user.getName(); } @Nullable public static String getUserName(@Nullable ApplicationUser user) { return user == null ? null : user.getUsername(); } @Nullable public static User getUserByKey(@Nullable String userKey) { ApplicationUser user = ApplicationUsers.byKey(userKey); return user == null ? null : user.getDirectoryUser(); } @Nullable public static User getUserByName(@Nullable String userName) { return ApplicationUsers.toDirectoryUser(ComponentAccessor.getUserManager().getUserByName(userName)); } @Nullable public static String getUserNameByKey(@Nullable String userKey) { User user = getUserByKey(userKey); return user == null ? null : user.getName(); } @Nullable public static String migrateUserNameToUserKey(@Nullable String userName) { return userName == null ? null : IdentifierUtils.toLowerCase(userName); } @Deprecated @Nullable public static User getUserOldObject(@Nullable ApplicationUser user) { return ApplicationUsers.toDirectoryUser(user); } /** * @return a fairly random number that is used to identify the scope of some other data for external components * with longer lifecycle (such as browsers or other addons). The signature is retrieved once per component * lifecycle and all other data it provides is valid only within the same signature. */ public static int createRuntimeSignature() { // todo for easier debugging and ensuring that the IDs are unique within the system, allocate a base number and then increment // static context is ok because the plugin is reloaded with the class long v = System.currentTimeMillis() + 1299827L * System.nanoTime() + 31 * new Object().hashCode(); return new Long(v).hashCode(); } public static String getDebugRowString(long row) { // todo return "(row " + row + ")"; } public static String getDebugRowString(StructureRow row) { // todo return row == null ? "" : row.toString(); } @NotNull public static String toJson(@Nullable Object object) { return toJson(object, defaultMapper()); } @NotNull public static String toJson(@Nullable Object object, ObjectMapper mapper) { if (object == null) return ""; try { return mapper.writeValueAsString(object); } catch (IOException e) { logger.warn("failed to serialize " + object, e); return ""; } } @Nullable public static T fromJson(String specJson, Class expectedClass) { return fromJson(specJson, expectedClass, defaultMapper()); } @Nullable public static T fromJson(String specJson, Class expectedClass, ObjectMapper mapper) { if (specJson == null || specJson.length() == 0) return null; try { return mapper.readValue(specJson, expectedClass); } catch (IOException e) { logger.warn("failed to deserialize " + specJson + " for " + expectedClass, e); return null; } } @Nullable public static T fromJson(String json, TypeReference typeRef) { return fromJson(json, typeRef, defaultMapper()); } @Nullable public static T fromJson(String json, TypeReference typeRef, ObjectMapper mapper) { if (json == null || json.isEmpty()) { return null; } try { return mapper.readValue(json, typeRef); } catch (IOException e) { logger.warn("failed to deserialize " + json + " for " + typeRef, e); return null; } } public static Map fromJson(String json) { return fromJson(json, defaultMapper()); } @Nullable public static Map fromJson(String json, ObjectMapper mapper) { return fromJson(json, JSON_MAP, mapper); } @Nullable public static T fromMap(Map json, Class expectedClass) { return fromMap(json, expectedClass, defaultMapper()); } @Nullable public static T fromMap(Map json, Class expectedClass, ObjectMapper mapper) { if (json == null || json.isEmpty()) { return null; } try { return mapper.convertValue(json, expectedClass); } catch (IllegalArgumentException e) { logger.warn("failed to convert " + json + " to " + expectedClass, e); return null; } } @Nullable public static Map toMap(Object object) { return toMap(object, defaultMapper()); } @Nullable public static Map toMap(Object object, ObjectMapper mapper) { try { return mapper.convertValue(object, JSON_MAP); } catch (IllegalArgumentException e) { logger.warn("failed to convert " + object + " to " + JSON_MAP, e); return null; } } public static ObjectMapper defaultMapper() { return new ObjectMapper().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL); } public static ObjectMapper withUnknownPropertiesMapper() { return defaultMapper().disable(FAIL_ON_UNKNOWN_PROPERTIES); } @SuppressWarnings("unchecked") public static Class> setType() { return (Class)Set.class; } @SuppressWarnings("unchecked") public static Class> mapType() { return (Class)Map.class; } @SuppressWarnings("unchecked") public static Class> listType() { return (Class)List.class; } public static boolean onlyOneIsTrue(boolean... booleans) { boolean result = false; for (boolean b : booleans) { if (b) { if (result) { result = false; break; } else { result = true; } } } return result; } public static void swap(List list, int a, int b) { T tmp = list.get(a); list.set(a, list.get(b)); list.set(b, tmp); } public static void warnExceptionIfDebug(Logger log, String message, Throwable t) { if (log.isDebugEnabled()) { log.warn(message, t); } else if (t != null) { log.warn(message + " " + getMessageWithCauseChain(t)); } else { log.warn(message); } } public static String getMessageWithCauseChain(Throwable t) { StringBuilder sb = new StringBuilder(); String pre = ""; while (t != null) { sb.append(pre).append(t); t = t.getCause(); pre = "; "; } return sb.toString(); } public static ItemDisplayable getItemDisplayable(ItemIdentity itemId, StructureAttributeService attributeService) { return getItemDisplayable(new ItemForestBuilderImpl().nextRow(itemId).build(), attributeService); } public static ItemDisplayable getItemDisplayable(StructureRow row, StructureAttributeService attributeService) { return getItemDisplayable(ImmutableItemForest.of(row), attributeService); } public static ItemDisplayable getItemDisplayable(ItemForest fragment, StructureAttributeService attributeService) { assert fragment.getForest().size() == 1 : fragment; VersionedRowValues values = attributeService.getAttributeValues( fragment, fragment.getForest().getRows(), Arrays.asList(CoreAttributeSpecs.DISPLAYABLE_TEXT, CoreAttributeSpecs.URL_TEXT)); long rowId = fragment.getForest().getRow(0); String description = values.get(rowId, CoreAttributeSpecs.DISPLAYABLE_TEXT); if (StringUtils.isEmpty(description)) { description = fragment.getRow(rowId).getItemId().toString(); } String url = values.get(rowId, CoreAttributeSpecs.URL_TEXT); return new ItemDisplayable(description, url); } @Nullable public static User getUserObject(Object object) { if (object instanceof User) return (User) object; if (object instanceof ApplicationUser) return ((ApplicationUser) object).getDirectoryUser(); return null; } @Nullable public static String getGroupName(Group group) { return group == null ? null : group.getName(); } public static boolean isMutuallyExclusive(LongList set1, LongList set2) { if (set2.isEmpty() || set1.isEmpty()) return true; LongList hashed, iterated; boolean insertedIsHashed = set1.size() < set2.size(); hashed = insertedIsHashed ? set1 : set2; iterated = insertedIsHashed ? set2 : set1; LongOpenHashSet set = LongOpenHashSet.createFrom(hashed); return !set.containsAny(iterated); } // todo add to integers public static boolean isMutuallyExclusive(LongSet set1, LongSet set2) { boolean set1Iterated = set1.size() < set2.size(); LongSet iterated = set1Iterated ? set1 : set2; LongSet checked = set1Iterated ? set2 : set1; return !checked.containsAny(iterated); } @NotNull public static String toDebugJson(@Nullable Object object) { if (object == null) return ""; if (object instanceof String) return (String)object; ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(object); } catch (IOException e) { return "?" + e + "?"; } } public static long nn(@Nullable Long value, long defaultValue) { return value == null ? defaultValue : value; } public static int nn(@Nullable Integer value, int defaultValue) { return value == null ? defaultValue : value; } @NotNull public static T nnv(@Nullable T value, @NotNull T defaultValue) { return value == null ? defaultValue : value; } public static long lv(String v, long defaultValue) { if (v == null) return defaultValue; try { return Long.parseLong(v); } catch (NumberFormatException e) { return defaultValue; } } public static int iv(String v, int defaultValue) { if (v == null) return defaultValue; try { return Integer.parseInt(v); } catch (NumberFormatException e) { return defaultValue; } } public static Long lvn(String v) { if (v == null) return null; try { return Long.parseLong(v); } catch (NumberFormatException e) { return null; } } public static String nn(String value) { return value == null ? "" : value; } public static long nnl(Long value) { return value == null ? 0L : value; } public static int nni(Integer value) { return value == null ? 0 : value; } @NotNull public static String getBaseUrl() { com.atlassian.sal.api.ApplicationProperties ap = ComponentAccessor.getOSGiComponentInstanceOfType( com.atlassian.sal.api.ApplicationProperties.class); if (ap != null) { return nn(ap.getBaseUrl()); } else { logger.warn("sal ApplicationProperties is null"); return ""; } } public static String decodeURL(String url) { try { return url == null ? "" : URLDecoder.decode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.error("NO UTF-8?", e); return url; } catch (IllegalArgumentException e) { logger.error("when decoding [" + url + "]", e); return url; } } public static String encodeURL(String url) { try { return url == null ? "" : URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.error("NO UTF-8?", e); return url; } catch (IllegalArgumentException e) { logger.error("when encoding [" + url + "]", e); return url; } } }