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.
package org.ligoj.app.plugin.bt.jira;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.sql.DataSource;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import javax.ws.rs.HttpMethod;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.ligoj.app.plugin.bt.BugTrackerResource;
import org.ligoj.app.plugin.bt.IdentifierHelper;
import org.ligoj.app.plugin.bt.jira.dao.JiraDao;
import org.ligoj.app.plugin.bt.jira.model.Workflow;
import org.ligoj.app.plugin.bt.model.ChangeItem;
import org.ligoj.app.plugin.bt.model.Sla;
import org.ligoj.app.resource.node.NodeResource;
import org.ligoj.app.resource.node.ParameterValueResource;
import org.ligoj.app.resource.subscription.SubscriptionResource;
import org.ligoj.bootstrap.core.INamableBean;
import org.ligoj.bootstrap.core.NamedBean;
import org.ligoj.bootstrap.core.curl.CurlProcessor;
import org.ligoj.bootstrap.core.curl.CurlRequest;
import org.ligoj.bootstrap.core.resource.TechnicalException;
import org.ligoj.bootstrap.core.validation.ValidationJsonException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
* Basic JIRA business features
*/
@Slf4j
public class JiraBaseResource {
/**
* Plug-in key.
*/
public static final String URL = BugTrackerResource.SERVICE_URL + "/jira";
/**
* Plug-in key.
*/
public static final String KEY = URL.replace('/', ':').substring(1);
/**
* Database JDBC URL key
*/
public static final String PARAMETER_JDBC_URL = KEY + ":jdbc-url";
/**
* Database JDBC Driver
*/
public static final String PARAMETER_JDBC_DRIVER = KEY + ":jdbc-driver";
/**
* Database user name
*/
public static final String PARAMETER_JDBC_USER = KEY + ":jdbc-user";
/**
* Database password
*/
public static final String PARAMETER_JDBC_PASSSWORD = KEY + ":jdbc-password";
/**
* Web site URL
*/
public static final String PARAMETER_URL = KEY + ":url";
/**
* JIRA internal identifier.
*/
public static final String PARAMETER_PROJECT = KEY + ":project";
/**
* JIRA external string identifier, AKA pkey.
*/
public static final String PARAMETER_PKEY = KEY + ":pkey";
/**
* JIRA user name able to perform index.
*/
public static final String PARAMETER_ADMIN_USER = KEY + ":user";
/**
* JIRA user password able to perform index.
*/
public static final String PARAMETER_ADMIN_PASSWORD = KEY + ":password";
/**
* Parameter corresponding to the associated version. Not yet saved in
* database, only in-memory to save request during status checks.
*/
protected static final String PARAMETER_CACHE_VERSION = KEY + ":version";
/**
* Workflow XML description pattern to match step-status identifiers.
*/
private static final Pattern STEP_PATTERN = Pattern.compile("");
private static final Pattern STATUS_PATTERN = Pattern.compile("(\\d+)");
/**
* Default 'jira' step to status mapping.
*/
private static final Workflow JIRA_WORKFLOW = new Workflow();
static {
final Map> mapping = new HashMap<>();
mapping.put("Open", new NamedBean<>(1, "Open"));
mapping.put("In Progress", new NamedBean<>(3, "In Progress"));
mapping.put("Resolved", new NamedBean<>(4, "Resolved"));
mapping.put("mapping", new NamedBean<>(5, "Reopened"));
mapping.put("Closed", new NamedBean<>(6, "Closed"));
JIRA_WORKFLOW.setStatusToSteps(mapping);
JIRA_WORKFLOW.setName("jira");
}
/**
* Entity manager.
*/
@PersistenceContext(type = PersistenceContextType.TRANSACTION, unitName = "pu")
protected EntityManager em;
@Getter
@Autowired
protected SubscriptionResource subscriptionResource;
@Autowired
protected JiraDao jiraDao;
@Autowired
protected IdentifierHelper identifierHelper;
@Autowired
protected NodeResource nodeResource;
@Autowired
protected ParameterValueResource pvResource;
/**
* Return the data source of JIRA database server.
*
* @param subscription
* The subscription used to retrieved the database parameters to
* build the datasource.
* @return the data source of JIRA database server.
*/
public DataSource getDataSource(final int subscription) {
return getDataSource(subscriptionResource.getParametersNoCheck(subscription));
}
/**
* Return the data source of JIRA database server.
*
* @param parameters
* the subscription parameters containing at least the data
* source configuration.
* @return the data source of JIRA database server.
*/
protected DataSource getDataSource(final Map parameters) {
try {
return new SimpleDriverDataSource(
(Driver) Class.forName(StringUtils.defaultIfBlank(parameters.get(PARAMETER_JDBC_DRIVER), "com.mysql.cj.jdbc.Driver"))
.getDeclaredConstructor().newInstance(),
StringUtils.defaultIfBlank(parameters.get(PARAMETER_JDBC_URL),
"jdbc:mysql://localhost:3306/jira6?useColumnNamesInFindColumn=true&useUnicode=yes&characterEncoding=UTF-8&autoReconnect=true&maxReconnects=3"),
parameters.get(PARAMETER_JDBC_USER), parameters.get(PARAMETER_JDBC_PASSSWORD));
} catch (final Exception e) {
log.error("Database connection issue for JIRA", e);
throw new TechnicalException("Database connection issue for JIRA", e);
}
}
/**
* Validate the database connectivity.
*
* @param parameters
* the JDBC parameters.
* @return the detected JIRA version.
*/
protected String validateDataBaseConnectivity(final Map parameters) {
if (parameters.containsKey(PARAMETER_ADMIN_USER)) {
// Clear the login failed status to clear the CAPTCHA status
jiraDao.clearLoginFailed(getDataSource(parameters), parameters.get(PARAMETER_ADMIN_USER));
}
return getVersion(parameters);
}
/**
* Return the version of JIRA from the database.
*
* @param parameters
* The parameters required to connect to the JIRA database.
* @return The version of JIRA from the database.
*/
@Transactional(value = TxType.SUPPORTS)
@org.springframework.transaction.annotation.Transactional(readOnly = true)
public String getVersion(final Map parameters) {
// Get previously resolved version if available or query it from SQL
return parameters.computeIfAbsent(PARAMETER_CACHE_VERSION, v -> jiraDao.getJiraVersion(getDataSource(parameters)));
}
/**
* Validate the administration connectivity.
*
* @param parameters
* the administration parameters.
* @return true when administration connection succeed.
*/
protected boolean validateAdminConnectivity(final Map parameters) {
final CurlProcessor processor = new JiraCurlProcessor();
try {
return authenticateAdmin(parameters, processor);
} finally {
processor.close();
}
}
/**
* Validate the project configuration.
*
* @param parameters
* the project parameters.
* @return project description.
*/
protected JiraProject validateProject(final Map parameters) {
// Get the project if it exists and with some statistics
final JiraProject project = jiraDao.getProject(getDataSource(parameters),
Integer.parseInt(ObjectUtils.defaultIfNull(parameters.get(PARAMETER_PROJECT), "0")));
if (project == null) {
// Invalid couple PKEY and id
throw new ValidationJsonException(PARAMETER_PKEY, "jira-project",
parameters.get(PARAMETER_PKEY) + "/" + parameters.get(PARAMETER_PROJECT));
}
return project;
}
/**
* Prepare an authenticated connection to JIRA
*
* @param parameters
* The subscription parameters.
* @param processor
* The current processor.
* @return true when authenticating as administrator succeed.
*/
protected boolean authenticateAdmin(final Map parameters, final CurlProcessor processor) {
final String user = parameters.get(PARAMETER_ADMIN_USER);
final String password = StringUtils.trimToEmpty(parameters.get(PARAMETER_ADMIN_PASSWORD));
final String baseUrl = parameters.get(PARAMETER_URL);
final String url = StringUtils.appendIfMissing(baseUrl, "/") + "login.jsp";
final List requests = new ArrayList<>();
requests.add(new CurlRequest(HttpMethod.GET, url, null));
requests.add(new CurlRequest(HttpMethod.POST, url,
"os_username=" + user + "&os_password=" + password + "&os_destination=&atl_token=&login=Connexion",
JiraCurlProcessor.LOGIN_CALLBACK, "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
// Sudoing is only required for JIRA 4+
if ("4".compareTo(getVersion(parameters)) <= 0) {
requests.add(
new CurlRequest(HttpMethod.POST, StringUtils.appendIfMissing(baseUrl, "/") + "secure/admin/WebSudoAuthenticate.jspa",
"webSudoIsPost=false&os_cookie=true&authenticate=Confirm&webSudoPassword=" + password,
JiraCurlProcessor.SUDO_CALLBACK, "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
}
return processor.process(requests);
}
/**
* Update the status, priorities, resolutions and types text of given SLA
* and retrieve all status texts involved of issues of given project.
*
* @param dataSource
* The data source of JIRA database.
* @param slas
* the SLA list containing involved status.
* @param jira
* the JIRA project identifier.
* @param changes
* Current changes. Used to computed required statuses.
* @return a {@link Map} where KEY is the status identifier and the VALUE is
* the status name, upper case and un-localized.
*/
protected Map updateIndentifierFromText(final DataSource dataSource, final List slas, final int jira,
final List changes) {
// Get all available priorities & types for given project
final Map types = jiraDao.getTypes(dataSource, jira);
final Map priorities = jiraDao.getPriorities(dataSource);
final Map resolutions = jiraDao.getResolutions(dataSource);
// Gather the involved statuses
final Set slaStatus = new HashSet<>();
for (final Sla sla : slas) {
// Add involved statuses of this SLA : start, stop, pause
slaStatus.addAll(identifierHelper.asList(sla.getPause()));
slaStatus.addAll(identifierHelper.asList(sla.getStart()));
slaStatus.addAll(identifierHelper.asList(sla.getStop()));
}
// Compute the identifiers from the texts
log.info("Get relevant text of {} statuses", slaStatus.size());
final Map allStatus = jiraDao.getStatuses(dataSource, getInvolvedStatuses(changes), slaStatus);
for (final Sla sla : slas) {
// Augment start/end/ignored status having a similar name
sla.setStartAsSet(identifierHelper.toIdentifiers(sla.getStart(), allStatus));
sla.setStopAsSet(identifierHelper.toIdentifiers(sla.getStop(), allStatus));
sla.setPausedAsSet(identifierHelper.toIdentifiers(sla.getPause(), allStatus));
// Update the priorities, resolutions & types
sla.setResolutionsAsSet(identifierHelper.toIdentifiers(sla.getResolutions(), resolutions));
sla.setPrioritiesAsSet(identifierHelper.toIdentifiers(sla.getPriorities(), priorities));
sla.setTypesAsSet(identifierHelper.toIdentifiers(sla.getTypes(), types));
}
return allStatus;
}
/**
* Return unique involved statues in the given changes.
*
* @param changes
* The list or resolved changes.
* @return unique involved statues in the given changes.
*/
protected Collection getInvolvedStatuses(final List extends ChangeItem> changes) {
final Collection statuses = new HashSet<>();
for (final ChangeItem change : changes) {
statuses.add(change.getFromStatus());
statuses.add(change.getToStatus());
statuses.add(change.getStatus());
}
return statuses;
}
/**
* Return workflow steps of given workflow's name and managing default
* 'jira' workflow. KEY is the status, VALUE is the corresponding workflow's
* step
*/
private Workflow getWorkflow(final String name, final String workflowXml, final Map statuses) {
if (workflowXml == null) {
// Default worflow
return JIRA_WORKFLOW;
}
final Map> statusToSteps = getWorkflowSteps(workflowXml, statuses);
final Workflow result = new Workflow();
result.setName(name);
result.setStatusToSteps(statusToSteps);
return result;
}
/**
* Return type to status to step mapping for all types valid for given
* project. KEY is the type, VALUE is the workflow mapping status and steps.
*
* @param dataSource
* The data source of JIRA database.
* @param jira
* the JIRA project identifier.
* @param statuses
* the worldwide available statuses.
* @return The mapping from type identifier to workflow.
*/
protected Map getTypeToStatusToStep(final DataSource dataSource, final int jira,
final Map statuses) {
final Map typeToWorkflow = jiraDao.getTypesToWorkflow(dataSource, jira);
final Map workflows = new HashMap<>();
final Map mapping = new HashMap<>();
for (final Map.Entry entry : typeToWorkflow.entrySet()) {
final String workflow = entry.getValue();
final Integer type = entry.getKey();
if (!workflows.containsKey(workflow)) {
// Not yet parsed workflow
final String workflowXml = jiraDao.getWorflow(dataSource, workflow);
workflows.put(workflow, getWorkflow(workflow, workflowXml, statuses));
}
mapping.put(type, workflows.get(workflow));
}
return mapping;
}
/**
* Return workflow steps of given workflow's name. KEY is the status, VALUE
* is the corresponding workflow's step
*/
private Map> getWorkflowSteps(final String workflowXml, final Map statuses) {
final Map> workflowSteps = new HashMap<>();
final String[] lines = StringUtils.replace(StringUtils.replace(workflowXml, "\\\"", "\""), "\\n", "\n").split("\\r?\\n");
NamedBean currentStep = null;
for (String line : lines) {
line = line.trim();
if (line.startsWith("")) {
final Matcher matcher = STATUS_PATTERN.matcher(line);
matcher.find();
workflowSteps.put(statuses.get(Integer.valueOf(matcher.group(1))), currentStep);
currentStep = null;
}
}
// Remove not used status by the current import
workflowSteps.remove(null);
return workflowSteps;
}
}