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

com.almworks.jira.structure.api.sync.AbstractSynchronizer Maven / Gradle / Ivy

There is a newer version: 17.25.3
Show newest version
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