com.almworks.jira.structure.api.sync.AbstractSynchronizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of structure-api Show documentation
Show all versions of structure-api Show documentation
Public API for the Structure Plugin for JIRA
package com.almworks.jira.structure.api.sync;
import com.almworks.jira.structure.api.StructureComponents;
import com.almworks.jira.structure.api.auth.StructureAuth;
import com.almworks.jira.structure.api.error.StructureException;
import com.almworks.jira.structure.api.error.StructureRuntimeException;
import com.almworks.jira.structure.api.forest.ForestSource;
import com.almworks.jira.structure.api.item.CoreIdentities;
import com.almworks.jira.structure.api.item.ItemIdentity;
import com.almworks.jira.structure.api.permissions.PermissionLevel;
import com.almworks.jira.structure.api.row.*;
import com.almworks.jira.structure.api.structure.StructureManager;
import com.almworks.jira.structure.api.sync.util.SyncLogger;
import com.almworks.jira.structure.api.util.*;
import com.atlassian.annotations.PublicSpi;
import com.atlassian.jira.issue.*;
import com.atlassian.jira.user.ApplicationUser;
import com.google.common.collect.ImmutableMap;
import org.codehaus.jackson.map.ObjectMapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
/**
* AbstractSynchronizer
is an abstract base class for the synchronizers that
* provides basic implementation for some of the {@link StructureSynchronizer} methods
* and offers some utility methods for the synchronizers.
*
* The type of the parameters used by this synchronizer is {@code Map}.
* The basic implementation of the {@link #storeParameters}
* and {@link #restoreParameters} methods use Jackson, expecting that map values
* are either basic Java types or are properly annotated (see, for example,
* {@link javax.xml.bind.annotation.XmlRootElement}). todo mention other kinds of annotation?
*
* This class also supports reading and writing some standard properties, such as:
*
* - The "source of truth" as a {@link SyncDirection} todo implement & explain
*
*
* todo describe audit log and createAuditLogEntryDescription()
*
* @author Igor Sereda
*/
@PublicSpi
public abstract class AbstractSynchronizer implements StructureSynchronizer {
private static final Logger logger = LoggerFactory.getLogger(AbstractSynchronizer.class);
// Important: not static so that ObjectMapper doesn't retain third-party parameter classes
private final ObjectMapper myObjectMapper = new ObjectMapper(); // todo setup it?
protected final StructureComponents myStructureComponents;
protected final StructureManager myStructureManager;
protected final RowManager myRowManager;
protected final SyncAuditLog myAuditLog;
private volatile IssueManager myIssueManager;
private volatile SynchronizerDescriptor myDescriptor;
/**
* Constructs an instance of the synchronizer.
*
* @param structureComponents services directory
*/
protected AbstractSynchronizer(StructureComponents structureComponents) {
myStructureComponents = structureComponents;
myAuditLog = structureComponents.getSyncAuditLog();
myStructureManager = structureComponents.getStructureManager();
myRowManager = structureComponents.getRowManager();
}
/**
* Called by the module descriptor on initialization.
*
* @param descriptor descriptor for this module
*/
public void init(SynchronizerDescriptor descriptor) {
myDescriptor = descriptor;
}
/**
* @return module descriptor, which can be used to retrieve configuration for this synchronizer
* from the atlassian-plugin.xml
*/
@NotNull
public SynchronizerDescriptor getDescriptor() {
return myDescriptor;
}
/**
* Looks up i18n text using the i18n bean from the module's plugin and the current
* user's locale.
*
* @param key text key
* @param parameters optional parameters
* @return the text or the key, if not found
*/
@NotNull
protected String getText(@NotNull String key, Object... parameters) {
return getDescriptor().getI18nBean().getText(key, parameters);
}
public void addDefaultFormParameters(@NotNull Map params) {
}
public String storeParameters(Object parameters) throws IOException {
if (parameters == null) return null;
if (!(parameters instanceof Map)) {
throw new IOException("Wrong parameters object type: " + parameters +
" (" + parameters.getClass().getName() + "), expected: Map");
}
return myObjectMapper.writeValueAsString(parameters);
}
public Map restoreParameters(String data) throws IOException {
if (data == null) return null;
return myObjectMapper.readValue(data, StructureUtil.mapType());
}
protected Map castParameters(Object p) {
if (p == null) return null;
if (!(p instanceof Map)) {
logger.warn(this + ": params of class " + p.getClass().getName() + " are not acceptable, expected: Map");
return null;
}
return StructureUtil.mapType().cast(p);
}
/**
* Returns a {@link MapObject} wrapper around the parameters map. {@code MapObject} can be useful for reading values.
* */
protected MapObject parametersAsMapObject(Object p) {
Map map = castParameters(p);
return map == null ? null : new MapObject(map);
}
@NotNull
protected MapObject parametersAsNNMapObject(Object p) {
MapObject mapObject = parametersAsMapObject(p);
return mapObject != null ? mapObject : MapObject.EMPTY;
}
/**
* Checks that the user has at least {@link PermissionLevel#EDIT} permission on the specified structure.
*
* @param structureId the ID of the structure
* @return true if the current user is allowed to modify the structure
*/
protected boolean verifyStructureEditPermissions(long structureId) {
return verifyStructureEditPermissions(structureId, SyncLogger.get());
}
/**
* Checks that the user has at least {@link PermissionLevel#EDIT} permission on the specified structure.
*
* @param structureId the ID of the structure
* @param log {@link SyncLogger logging helper} that will be used to log warning in case the structure does not exist or is not accessible;
* in case you don't need synchronizer information in the logs, you can use {@link #verifyStructureEditPermissions(long)}
* @return true if the current user is allowed to modify the structure
*/
protected boolean verifyStructureEditPermissions(long structureId, SyncLogger log) {
boolean r = myStructureManager.isAccessible(structureId, PermissionLevel.EDIT);
if (!r) {
log.warn("cannot run under user", StructureUtil.username(getCurrentUser()), "because he or she does not have permissions to edit the structure");
}
return r;
}
private ApplicationUser getCurrentUser() {
return StructureAuth.getUser();
}
/**
* Retrieves an instance of Issue
.
*
* @param issueId the ID of the issue
* @return the issue, or null if the issue cannot be found or there is an exception getting it
*/
@Nullable
protected Issue getIssue(long issueId) {
MutableIssue issueObject = null;
try {
issueObject = getIssueManager().getIssueObject(issueId);
} catch (Exception e) {
logger.warn("cannot retrieve issue " + issueId + ": " + e);
}
return issueObject;
}
/**
* Retrieves an instance of issue by issue key.
*
* @param key issue key
* @return the issue, or null if the issue cannot be found or there is an exception getting it
*/
@Nullable
protected Issue getIssue(@NotNull String key) {
MutableIssue issueObject = null;
try {
issueObject = getIssueManager().getIssueObject(key);
} catch (Exception e) {
logger.warn("cannot retrieve issue " + key + ": " + e);
}
return issueObject;
}
@NotNull
protected IssueManager getIssueManager() {
IssueManager issueManager = myIssueManager;
if (issueManager == null) {
myIssueManager = issueManager = JiraComponents.getIssueManager();
if (issueManager == null) {
throw new StructureRuntimeException("IssueManager is not available");
}
}
return issueManager;
}
@Nullable
protected Issue getIssueByRowId(long rowId) {
long issueId = getIssueIdByRowId(rowId);
return issueId == 0 ? null : getIssue(issueId);
}
protected final long getIssueIdByRowId(long rowId) {
try {
return getIssueIdByRow(myRowManager.getRow(rowId));
} catch (MissingRowException e) {
SyncLogger.get().warnException(e);
}
return 0L;
}
public static long getIssueIdByRow(StructureRow structureRow) {
ItemIdentity itemId = structureRow.getItemId();
if (CoreIdentities.isIssue(itemId)) {
return itemId.getLongId();
}
return 0L;
}
@Override
public void sync(@NotNull SyncInstance instance, @NotNull IncrementalSyncData syncData,
@NotNull ForestSource forestSource) throws StructureException
{
SyncRunAuditEntry result = doSync(instance, syncData, forestSource);
List actions = result.getActions();
if (myAuditLog.isActionGroupRecorded(actions)) {
MapObject auditEntryDesc = SyncAuditLogHelper.createAuditLogEntryDescription(instance, syncData, result.getDescription());
myAuditLog.recordActions(instance, auditEntryDesc, actions);
}
}
@Override
public void resync(@NotNull SyncInstance instance, @NotNull ForestSource forestSource)
throws StructureException
{
SyncRunAuditEntry result = doResync(instance, forestSource);
List actions = result.getActions();
if (myAuditLog.isActionGroupRecorded(actions)) {
MapObject auditEntryDesc = SyncAuditLogHelper.createAuditLogEntryDescription(instance, null, result.getDescription());
myAuditLog.recordActions(instance, auditEntryDesc, actions);
}
}
@NotNull
protected abstract SyncRunAuditEntry doSync(@NotNull SyncInstance instance, @NotNull IncrementalSyncData data,
@NotNull ForestSource forestSource) throws StructureException;
@NotNull
protected abstract SyncRunAuditEntry doResync(@NotNull SyncInstance instance, @NotNull ForestSource forestSource)
throws StructureException;
protected static SyncRunAuditEntry success(List actions) {
return new SyncRunAuditEntry.Success(actions);
}
protected static SyncRunAuditEntry failure(String reason) {
return new SyncRunAuditEntry.Failure(null, reason);
}
protected static SyncRunAuditEntry failure(Throwable throwable) {
return new SyncRunAuditEntry.Failure(throwable, null);
}
protected static SyncRunAuditEntry failure(Throwable throwable, String reason) {
return new SyncRunAuditEntry.Failure(throwable, reason);
}
protected interface SyncRunAuditEntry {
List getActions();
Map getDescription();
class Success implements SyncRunAuditEntry {
private final List myActions;
private static final Map DESCRIPTION = ImmutableMap.of("result", "success");
public Success(List actions) {
myActions = actions;
}
@Override
public List getActions() {
return myActions;
}
@Override
public Map getDescription() {
return DESCRIPTION;
}
}
class Failure implements SyncRunAuditEntry {
private final Throwable myThrowable;
private final String myReason;
public Failure(Throwable throwable, String reason) {
myThrowable = throwable;
myReason = reason;
}
@Override
public List getActions() {
return Collections.emptyList();
}
@Override
public Map getDescription() {
return SyncAuditLogHelper.getFailureDescription(myReason, myThrowable);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy