
com.almworks.jira.structure.api2g.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.api2g.sync;
import com.almworks.jira.structure.api.*;
import com.almworks.jira.structure.api2g.item.CoreIdentities;
import com.almworks.jira.structure.api2g.item.ItemIdentity;
import com.almworks.jira.structure.api2g.row.RowManager;
import com.almworks.jira.structure.api2g.row.StructureRow;
import com.almworks.jira.structure.api2g.structure.StructureManager;
import com.almworks.jira.structure.api2g.v2.MissingRowException;
import com.almworks.jira.structure.api2g.v2.UpdatableForestSource;
import com.almworks.jira.structure.util.*;
import com.atlassian.annotations.PublicSpi;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.user.ApplicationUsers;
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.*;
import static com.almworks.jira.structure.api2g.sync.SyncAuditLogHelper.createAuditLogEntryDescription;
import static com.almworks.jira.structure.api2g.sync.SyncAuditLogHelper.getFailureDescription;
/**
* 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 StructureServices myStructureServices;
protected final StructureManager myStructureManager;
protected final JiraAuthenticationContext myAuthenticationContext;
protected final IssueManager myIssueManager;
protected final RowManager myRowManager;
protected final SyncAuditLog myAuditLog;
private StructureSynchronizerModuleDescriptor myDescriptor;
/**
* Constructs an instance of the synchronizer.
* @param structureServices services facade
* @param rowManager todo take from structureServices
* @param auditLog
*
*/
protected AbstractSynchronizer(StructureServices structureServices, RowManager rowManager, SyncAuditLog auditLog) {
myStructureServices = structureServices;
myAuditLog = auditLog;
myStructureManager = structureServices.getStructureManager();
myRowManager = rowManager;
myAuthenticationContext = structureServices.getAuthenticationContext();
myIssueManager = structureServices.getIssueManager();
}
/**
* Called by the module descriptor on initialization
*
* @param descriptor descriptor for this module
*/
public synchronized void init(StructureSynchronizerModuleDescriptor descriptor) {
myDescriptor = descriptor;
}
/**
* @return module descriptor, which can be used to retrieve configuration for this synchronizer
* from the atlassian-plugin.xml
*/
@NotNull
public synchronized StructureSynchronizerModuleDescriptor 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 User getCurrentUser() {
ApplicationUser user = myAuthenticationContext.getUser();
return ApplicationUsers.toDirectoryUser(user);
}
/**
* 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 MutableIssue getIssue(long issueId) {
MutableIssue issueObject = null;
try {
issueObject = myIssueManager.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 MutableIssue getIssue(@NotNull String key) {
MutableIssue issueObject = null;
try {
issueObject = myIssueManager.getIssueObject(key);
} catch (Exception e) {
logger.warn("cannot retrieve issue " + key + ": " + e);
}
return issueObject;
}
@Nullable
protected MutableIssue 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 UpdatableForestSource forestSource) throws StructureException
{
SyncRunAuditEntry result = doSync(instance, syncData, forestSource);
MapObject auditEntryDesc = createAuditLogEntryDescription(instance, syncData, result.getDescription());
myAuditLog.recordActions(instance, auditEntryDesc, result.getActions());
}
@Override
public void resync(@NotNull SyncInstance instance, @NotNull UpdatableForestSource forestSource)
throws StructureException
{
SyncRunAuditEntry result = doResync(instance, forestSource);
MapObject auditEntryDesc = createAuditLogEntryDescription(instance, null, result.getDescription());
myAuditLog.recordActions(instance, auditEntryDesc, result.getActions());
}
@NotNull
protected abstract SyncRunAuditEntry doSync(@NotNull SyncInstance instance, @NotNull IncrementalSyncData data,
@NotNull UpdatableForestSource forestSource) throws StructureException;
@NotNull
protected abstract SyncRunAuditEntry doResync(@NotNull SyncInstance instance, @NotNull UpdatableForestSource 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 getFailureDescription(myReason, myThrowable);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy