Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.almworks.jira.structure.api.util.StructureUtil Maven / Gradle / Ivy
package com.almworks.jira.structure.api.util;
import com.almworks.integers.*;
import com.almworks.jira.structure.api.attribute.*;
import com.almworks.jira.structure.api.auth.StructureAuth;
import com.almworks.jira.structure.api.error.StructureException;
import com.almworks.jira.structure.api.i18n.JiraI18n;
import com.almworks.jira.structure.api.item.ItemIdentity;
import com.almworks.jira.structure.api.permissions.PermissionLevel;
import com.almworks.jira.structure.api.permissions.PermissionRule;
import com.almworks.jira.structure.api.row.StructureRow;
import com.almworks.jira.structure.api.structure.Structure;
import com.almworks.jira.structure.api.structure.StructureManager;
import com.atlassian.annotations.Internal;
import com.atlassian.crowd.embedded.api.Group;
import com.atlassian.crowd.embedded.impl.IdentifierUtils;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.issue.*;
import com.atlassian.jira.user.ApplicationUser;
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.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 java.util.function.Supplier;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
/**
* 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 PermissionLevel applyPermissions(List permissions, ApplicationUser 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: " + 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() : 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();
}
/**
* Similar to {@link Boolean#getBoolean(String)}, but with ability to specify default value if no value is specified.
*/
public static boolean getBooleanSystemProperty(String key, boolean defaultValue) {
boolean result = defaultValue;
try {
String propertyValue = System.getProperty(key);
if (propertyValue != null) {
result = Boolean.parseBoolean(propertyValue);
}
} catch (IllegalArgumentException | NullPointerException e) {
}
return result;
}
/**
* 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 extends PermissionRule> permissions) {
return permissions == null ? null : copyPermissions(permissions);
}
@NotNull
public static List copyPermissions(@Nullable Collection extends PermissionRule> 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 = JiraComponents.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 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() + ")";
}
@NotNull
public static String getTextInCurrentUserLocale(String key, Object... parameters) {
return JiraI18n.forCurrentUser().getText(key, parameters);
}
@NotNull
public static String getTextInCurrentUserLocale(@NotNull I18nText i18nText) {
return getTextInCurrentUserLocale(i18nText.getI18nKey(), i18nText.getArguments());
}
/**
* Formats the text taken from the i18n bundle with the specified parameters in the locale that is selected as follows:
*
* If locale is not null , it is used.
* Otherwise, if user is not null , the user locale (set up in the user preferences) is used.
* 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.
*
*
* @param messageParameters can be the same as {@link I18nHelper#getText(String, Object)}
*/
@NotNull
public static String getText(@Nullable final Locale locale, @Nullable final ApplicationUser user,
final String textKey,
final Object... messageParameters)
{
return JiraI18n.create(locale, user).getText(textKey, messageParameters);
}
@NotNull
public static Locale getJiraDefaultLocale() {
ApplicationProperties appProps = JiraComponents.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 ApplicationUser user) {
return user == null ? null : user.getKey();
}
@Nullable
public static String getUserName(@Nullable ApplicationUser user) {
return user == null ? null : user.getUsername();
}
@Nullable
public static ApplicationUser getUserByKey(@Nullable String userKey) {
return JiraFunc.USERKEY_APPLICATIONUSER.la(userKey);
}
@Nullable
public static ApplicationUser getApplicationUserByName(@Nullable String userName) {
return JiraComponents.getUserManager().getUserByName(userName);
}
@Nullable
public static String getUserNameByKey(@Nullable String userKey) {
ApplicationUser user = getUserByKey(userKey);
return user == null ? null : user.getName();
}
@Nullable
public static String migrateUserNameToUserKey(@Nullable String userName) {
return userName == null ? null : IdentifierUtils.toLowerCase(userName);
}
/**
* @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 JsonUtil.toJson(object);
}
@NotNull
public static String toJson(@Nullable Object object, ObjectMapper mapper) {
return JsonUtil.toJson(object, mapper);
}
@Nullable
public static T fromJson(String specJson, Class expectedClass) {
return JsonUtil.fromJson(specJson, expectedClass);
}
@Nullable
public static T fromJson(String specJson, Class expectedClass, ObjectMapper mapper) {
return JsonUtil.fromJson(specJson, expectedClass, mapper);
}
@Nullable
public static T fromJson(String json, TypeReference typeRef) {
return JsonUtil.fromJson(json, typeRef);
}
@Nullable
public static T fromJson(String json, TypeReference typeRef, ObjectMapper mapper) {
return JsonUtil.fromJson(json, typeRef, mapper);
}
@Nullable
public static Map fromJson(String json) {
return JsonUtil.fromJson(json);
}
@Nullable
public static Map fromJson(String json, ObjectMapper mapper) {
return JsonUtil.fromJson(json, 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) {
return JsonUtil.fromMap(json, expectedClass, mapper);
}
@Nullable
public static Map toMap(Object object) {
return JsonUtil.toMap(object);
}
@Nullable
public static Map toMap(Object object, ObjectMapper mapper) {
return JsonUtil.toMap(object, mapper);
}
public static ObjectMapper defaultMapper() {
return JsonUtil.defaultMapper();
}
public static ObjectMapper withUnknownPropertiesMapper() {
return JsonUtil.withUnknownPropertiesMapper();
}
public static Class> setType() {
return TypeUtils.setType();
}
public static Class> mapType() {
return TypeUtils.mapType();
}
public static Class> listType() {
return TypeUtils.listType();
}
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) {
LoggingUtils.warnExceptionIfDebug(log, message, t);
}
public static String getMessageWithCauseChain(Throwable t) {
return LoggingUtils.getMessageWithCauseChain(t);
}
@Nullable
public static String getItemDescription(ItemIdentity itemId) {
try {
StructureAttributeService attributeService = JiraComponents.getOSGiComponentInstanceOfType(StructureAttributeService.class);
if (attributeService != null) {
ItemDisplayable displayable = getItemDisplayable(itemId, attributeService);
return displayable.getDescription();
}
} catch (Exception | LinkageError ignored) {
}
return null;
}
@NotNull
public static ItemDisplayable getItemDisplayable(@NotNull ItemIdentity itemId, StructureAttributeService attributeService) {
ItemDisplayable result = getItemDisplayable(singleton(itemId), attributeService).get(itemId);
assert result != null;
return result;
}
@NotNull
public static ItemDisplayable getItemDisplayable(@NotNull StructureRow row, StructureAttributeService attributeService) {
return getItemDisplayable(row.getItemId(), attributeService);
}
@NotNull
public static Map getItemDisplayable(@NotNull Collection itemIds,
StructureAttributeService attributeService)
{
ItemValues values = attributeService.getItemValues(itemIds, asList(CoreAttributeSpecs.DISPLAYABLE_TEXT, CoreAttributeSpecs.URL));
Map result = new HashMap<>();
for (ItemIdentity itemId : itemIds) {
String description = values.get(itemId, CoreAttributeSpecs.DISPLAYABLE_TEXT);
if (StringUtils.isEmpty(description)) {
description = itemId.toString();
}
String url = values.get(itemId, CoreAttributeSpecs.URL);
result.put(itemId, new ItemDisplayable(description, url));
}
return result;
}
@Nullable
public static String getGroupName(Group group) {
return group == null ? null : group.getName();
}
public static boolean isMutuallyExclusive(LongList list1, LongList list2) {
return IntegersUtil.isMutuallyExclusive(list1, list2);
}
public static boolean isMutuallyExclusive(LongSet set1, LongSet set2) {
return IntegersUtil.isMutuallyExclusive(set1, set2);
}
@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 | OutOfMemoryError e) {
// out of memory was seen when trying to serialize a VERY large response in a stress test
return "?" + e + "?";
}
}
public static long nn(@Nullable Long value, long defaultValue) {
return TypeUtils.nn(value, defaultValue);
}
public static int nn(@Nullable Integer value, int defaultValue) {
return TypeUtils.nn(value, defaultValue);
}
@NotNull
public static T nnv(@Nullable T value, @NotNull T defaultValue) {
return TypeUtils.nnv(value, defaultValue);
}
public static long lv(String v, long defaultValue) {
return TypeUtils.lv(v, defaultValue);
}
public static int iv(String v, int defaultValue) {
return TypeUtils.iv(v, defaultValue);
}
public static Long lvn(String v) {
return TypeUtils.lvn(v);
}
public static Integer ivn(String v) {
return TypeUtils.ivn(v);
}
public static String nn(String value) {
return TypeUtils.nn(value);
}
public static long nnl(Long value) {
return TypeUtils.nnl(value);
}
public static int nni(Integer value) {
return TypeUtils.nni(value);
}
@NotNull
public static String getBaseUrl() {
com.atlassian.sal.api.ApplicationProperties ap = JiraComponents.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;
}
}
public static String encodeURIComponent(String cmp) {
return encodeURL(cmp)
.replaceAll("\\+", "%20")
.replaceAll("%21", "!")
.replaceAll("%27", "'")
.replaceAll("%28", "(")
.replaceAll("%29", ")")
.replaceAll("%7E", "~");
}
public static boolean isSubMap(Map map, Map subMap) {
return MapUtils.isSubMap(map, subMap);
}
@Internal
public static boolean isDevMode() {
return "true".equals(System.getProperty("jira.dev.mode")) || "true".equals(System.getProperty("atlassian.dev.mode"));
}
@NotNull
public static String nonBlank(@Nullable String value) throws IllegalArgumentException {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("argument cannot be null or blank");
}
return value.trim();
}
/**
* Not thread safe
*/
public static Supplier memoize(Supplier supplier) {
if (supplier instanceof MemoizingSupplier) return supplier;
return new MemoizingSupplier<>(supplier);
}
private static class MemoizingSupplier implements Supplier {
private Supplier mySupplier;
private T myValue;
private boolean myInitialized;
public MemoizingSupplier(Supplier supplier) {
mySupplier = supplier;
}
@Override
public T get() {
if (!myInitialized) {
myInitialized = true;
Supplier supplier = mySupplier;
mySupplier = null; // no need to hold it in memory
myValue = supplier.get();
}
return myValue;
}
}
}