Java tutorial
/* * Copyright 2009 CollabNet, Inc. ("CollabNet") Licensed under the Apache * License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.collabnet.ccf.pi.qc.v90; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.collabnet.ccf.core.ArtifactState; import com.collabnet.ccf.core.CCFRuntimeException; import com.collabnet.ccf.core.ga.GenericArtifact; import com.collabnet.ccf.core.ga.GenericArtifact.ArtifactModeValue; import com.collabnet.ccf.core.ga.GenericArtifactField; import com.collabnet.ccf.core.utils.DateUtil; import com.collabnet.ccf.core.utils.GATransformerUtil; import com.collabnet.ccf.core.utils.JerichoUtils; import com.collabnet.ccf.pi.qc.v90.api.DefectAlreadyLockedException; import com.collabnet.ccf.pi.qc.v90.api.IBug; import com.collabnet.ccf.pi.qc.v90.api.IBugFactory; import com.collabnet.ccf.pi.qc.v90.api.IConnection; import com.collabnet.ccf.pi.qc.v90.api.IFactoryList; import com.collabnet.ccf.pi.qc.v90.api.IFilter; import com.collabnet.ccf.pi.qc.v90.api.IRecordSet; import com.collabnet.ccf.pi.qc.v90.api.IRequirement; import com.collabnet.ccf.pi.qc.v90.api.IRequirementsFactory; import com.collabnet.ccf.pi.qc.v90.api.IVersionControl; import com.collabnet.ccf.pi.qc.v90.api.dcom.Bug; import com.collabnet.ccf.pi.qc.v90.api.dcom.Requirement; import com.jacob.com.ComFailException; /** * The Defect handler class provides support for listing and/or edit defects . * */ public class QCHandler { private static final String QC_END_HTML_TAGS = "\\s*</body>\\s*</html>\\s*$"; private static final String QC_START__HTML_TAGS = "^\\s*<html>\\s*<body>\\s*"; private static final String QC_LINES_WITH_NON_BREAKING_SPACE = "<br /> "; private static final String QC_TAG_WITH_EMPTY_LINE = "</span></font></div>\\s*<div align=\"left\"><font face=\"Arial\"><span style=\"font-size:8pt\"><br />"; private static final String QC_FIRST_BREAK = "<br[^>]*>"; private static final String QC_COMMENT_SEPARATOR = "(?i)<font[^>]*>(?:<span[^>]*>)?<b>_+</b>(?:</span>)?</font>"; private static final String QC_FIRST_COMMENT_PREFIX = "<div align=\"left\"><font face=\"Arial\"><span style=\"font-size:8pt\"> </span></font></div>"; private static final String QC_BREAK = "<br>"; private static final String QC_LINE_BREAK = "<br />\r\n"; private static final String QC_COMMENT_PREFIX = "<div align="; private static final String QC12_LINE_BREAK = "\r"; private static final String QC_VERSION_OTHERS_END = "</b></font>"; private static final String QC_VERSION_OTHERS_START = "<font color=\"#000080\"><b>"; private static final String QC11_FIRST_COMMENT_START = "<div align=\"left\"><font face=\"Arial\" color=\"#000080\"><span style=\"font-size:8pt\"><b>%s, %s:</b></span></font><font face=\"Arial\"><span style=\"font-size:8pt\">%s</span></font></div>"; private static final String QC12_INITIAL_COMMENT_START = "<div align=\"left\"><font face=\"Arial\" color=\"#000080\"><span style=\"font-size:8pt\"> <b>%s, %s:</b></span></font><font face=\"Arial\"><span style=\"font-size:8pt\">%s</span></font></div>"; private static final String QC_COMMENT_END = "<span style=\"font-size:10pt\"><b>________________________________________</b></span></font><font \r\nface=\"Arial\"><span style=\"font-size:8pt\"><br />\r\n</span></font><font face=\"Arial\" color=\"#000080\">" + "<span style=\"font-size:8pt\"><b>%s, %s:</b></span></font><font face=\"Arial\"><span style=\"font-size:8pt\">%s</span></font></div>"; private static final String QC_COMMENT_START = "<div align=\"left\"><font face=\"Arial\"><span style=\"font-size:8pt\"><br />\n</span></font>"; private static final String QC11_FONT_TAG = "<font face=\"Arial\" color=\"#000080\" size=\"+0\">"; private static final String QC12_FONT_TAG = "<font face=\"Arial\" color=\"#000080\">"; private static final String QC_VERSION_11 = "11"; private static final String QC_VERSION_12 = "12"; private boolean useAlternativeFieldName = false; private static final String QC11_LAST_TAGS = "\r\n</body>\r\n</html>"; private static final String QC11_FIRST_TAGS = "<html>\r\n<body>\r\n"; private static final String FIRST_TAGS = "<html><body>"; private static final String LAST_TAGS = "</body></html>"; private static final Log log = LogFactory.getLog(QCHandler.class); // private QCAttachmentHandler attachmentHandler; private final static String QC_BUG_ID = "BG_BUG_ID"; private final static String QC_REQ_ID = "RQ_REQ_ID"; private final static String QC_REQ_TYPE_ID = "RQ_TYPE_ID"; private final static String QC_BUG_VER_STAMP = "BG_BUG_VER_STAMP"; private final static String QC_BG_ATTACHMENT = "BG_ATTACHMENT"; private final static String QC_RQ_ATTACHMENT = "RQ_ATTACHMENT"; private final static String QC_BG_VTS = "BG_VTS"; private final static String QC_RQ_VTS = "RQ_VTS"; private final static String UNDERSCORE_STRING = "<font color=\"#000080\"><b>________________________________________</b></font>"; public static final String REQUIREMENT_TYPE_ALL = "ALL"; private final static String QC_UNDERSCORE_PREFIX = "<br>________________________________________"; public QCHandler(boolean useAlternativeFieldName) { setUseAlternativeFieldName(useAlternativeFieldName); } /** * updates the artifact's last modification date to the one passed into the * method. * * This is necessary, because the modification date may have changed between * when the last transaction to be processed was determined and the point in * time when the artifact is processed. * * @param artifact * the artifact to adjust. * @param lastModifiedDate * the date to set for the artifact. */ public void adjustLastModificationDate(GenericArtifact artifact, Date lastModifiedDate, boolean isDefect) { String fieldName = isDefect ? "BG_VTS" : "RQ_VTS"; List<GenericArtifactField> genArtifactFields = artifact .getAllGenericArtifactFieldsWithSameFieldName(fieldName); if (genArtifactFields != null && genArtifactFields.get(0) != null) { genArtifactFields.get(0).setFieldValue(lastModifiedDate); } String lastModifiedDateStr = DateUtil.format(lastModifiedDate); artifact.setSourceArtifactLastModifiedDate(lastModifiedDateStr); } /** * Assigns values of the incoming parameters to the incoming genericArtifact * * @param latestDefectArtifact * The GenericArtifact to which the following values need to be * assigned. * @param sourceArtifactId * @param sourceRepositoryId * @param sourceRepositoryKind * @param sourceSystemId * @param sourceSystemKind * @param targetRepositoryId * @param targetRepositoryKind * @param targetSystemId * @param targetSystemKind * @param thisTransactionId */ public void assignValues(GenericArtifact latestDefectArtifact, String sourceArtifactId, String sourceRepositoryId, String sourceRepositoryKind, String sourceSystemId, String sourceSystemKind, String targetRepositoryId, String targetRepositoryKind, String targetSystemId, String targetSystemKind, String thisTransactionId, String sourceSystemTimezone, String targetSystemTimezone) { latestDefectArtifact.setSourceArtifactId(sourceArtifactId); latestDefectArtifact.setSourceRepositoryId(sourceRepositoryId); latestDefectArtifact.setSourceRepositoryKind(sourceRepositoryKind); latestDefectArtifact.setSourceSystemId(sourceSystemId); latestDefectArtifact.setSourceSystemKind(sourceSystemKind); latestDefectArtifact.setSourceSystemTimezone(sourceSystemTimezone); latestDefectArtifact.setTargetRepositoryId(targetRepositoryId); latestDefectArtifact.setTargetRepositoryKind(targetRepositoryKind); latestDefectArtifact.setTargetSystemId(targetSystemId); latestDefectArtifact.setTargetSystemKind(targetSystemKind); latestDefectArtifact.setSourceArtifactVersion(thisTransactionId); latestDefectArtifact.setTargetSystemTimezone(targetSystemTimezone); } /** * Create the defect based on the incoming field values * * @param qcc * The Connection object * @param List * <GenericArtifactField> The values of each fields of the defect * that need to be used while creation. * * @return IQCDefect Created defect object * */ public IQCDefect createDefect(IConnection qcc, List<GenericArtifactField> allFields, String connectorUser, String targetSystemTimezone) throws RemoteException { IBugFactory bugFactory = null; IBug bug = null; Map<String, String> qcDefectSchemaMetadataCache = null; if (isUseAlternativeFieldName()) { GenericArtifact genericArtifactQCSchemaFields = QCConfigHelper.getSchemaFieldsForDefect(qcc); qcDefectSchemaMetadataCache = getQCSchemaMetadataMap(genericArtifactQCSchemaFields); } try { bugFactory = qcc.getBugFactory(); bug = bugFactory.addItem(null); bug.lockObject(); List<String> allFieldNames = new ArrayList<String>(); String fieldValue = null; for (int cnt = 0; cnt < allFields.size(); cnt++) { GenericArtifactField thisField = allFields.get(cnt); String fieldName = thisField.getFieldName(); if (isUseAlternativeFieldName()) { fieldName = getTechnicalNameForQCField(qcDefectSchemaMetadataCache, fieldName); } if (thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATE) || thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATETIME)) fieldValue = getProperFieldValue(thisField, targetSystemTimezone); else fieldValue = (String) thisField.getFieldValue(); if (fieldName.equals(QCConfigHelper.QC_BG_DEV_COMMENTS)) { String oldFieldValue = bug.getFieldAsString(fieldName); if ((!StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue) && !oldFieldValue.equals(fieldValue)) || (StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue))) { fieldValue = getConcatinatedCommentValue(oldFieldValue, fieldValue, connectorUser, qcc); } } /* * The following fields cannot be set or have some conditions * Cannot be set from here: 1. BG_BUG_ID 2. BG_BUG_VER_STAMP 3. * BG_VTS Has some conditions: 1. BG_SUBJECT -> Can be set to a * Valid value that is present in the list. */ if (!(allFieldNames.contains(fieldName)) && !(fieldName.equals(QC_BUG_ID) || fieldName.equals(QC_BUG_VER_STAMP) || fieldName.equals(QC_BG_ATTACHMENT) || fieldName.equals(QC_BG_VTS) || fieldName.endsWith(QCConfigHelper.HUMAN_READABLE_SUFFIX))) { try { bug.setField(fieldName, fieldValue); } catch (Exception e) { String message = "Exception while setting the value of field " + fieldName + " to " + fieldValue + ": " + e.getMessage(); log.error(message, e); throw new CCFRuntimeException(message, e); } } else { log.debug(fieldName); } if (!fieldName.equals(QCConfigHelper.QC_BG_DEV_COMMENTS)) allFieldNames.add(fieldName); } bug.post(); } catch (Exception e) { String bugId = null; if (bug != null) { bugId = bug.getId(); bugFactory.removeItem(bugId); bug = null; } String message = "Exception while creating Bug " + bugId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } finally { if (bug != null) { bug.unlockObject(); } bugFactory = null; } return new QCDefect((Bug) bug); } public IQCRequirement createRequirement(IConnection qcc, List<GenericArtifactField> allFields, String connectorUser, String targetSystemTimezone, String informalRequirementsType, String parentArtifactId) { IRequirementsFactory reqFactory = null; IRequirement req = null; IVersionControl versionControl = null; boolean versionControlSupported = false; Map<String, String> qcRequirementSchemaMetadataCache = null; informalRequirementsType = configureRequirementsType(allFields, informalRequirementsType); if (isUseAlternativeFieldName()) { String technicalReleaseTypeId = QCHandler.getRequirementTypeTechnicalId(qcc, informalRequirementsType); GenericArtifact genericArtifactQCSchemaFields = QCConfigHelper.getSchemaFieldsForRequirement(qcc, technicalReleaseTypeId); qcRequirementSchemaMetadataCache = getQCSchemaMetadataMap(genericArtifactQCSchemaFields); } try { reqFactory = qcc.getRequirementsFactory(); if (parentArtifactId != null && !parentArtifactId.equals(GenericArtifact.VALUE_UNKNOWN) && !parentArtifactId.equals(GenericArtifact.VALUE_NONE)) { req = reqFactory.addItem(parentArtifactId); } else { // we will create every requirement under the requirement root req = reqFactory.addItem("0"); } req.lockObject(); versionControl = req.getVersionControlObject(); if (versionControl != null) { versionControlSupported = ccfCheckoutReq(qcc, req, versionControl); } List<String> allFieldNames = new ArrayList<String>(); String fieldValue = null; // make sure requirement type is set early, to avoid unknown req // type - ccf395 req.setTypeId(informalRequirementsType); for (int cnt = 0; cnt < allFields.size(); cnt++) { GenericArtifactField thisField = allFields.get(cnt); String fieldName = thisField.getFieldName(); if (isUseAlternativeFieldName()) { fieldName = getTechnicalNameForQCField(qcRequirementSchemaMetadataCache, fieldName); } if (thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATE) || thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATETIME)) fieldValue = getProperFieldValue(thisField, targetSystemTimezone); else fieldValue = (String) thisField.getFieldValue(); if (fieldName.equals(QCConfigHelper.QC_RQ_DEV_COMMENTS)) { String oldFieldValue = req.getFieldAsString(fieldName); if ((!StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue) && !oldFieldValue.equals(fieldValue)) || (StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue))) { fieldValue = getConcatinatedCommentValue(oldFieldValue, fieldValue, connectorUser, qcc); } } if (!(allFieldNames.contains(fieldName)) && !(fieldName.equals(QC_REQ_ID) || fieldName.equals(QC_RQ_ATTACHMENT) || fieldName.equals(QC_RQ_VTS) || fieldName.endsWith(QCConfigHelper.HUMAN_READABLE_SUFFIX))) { try { if ("RQ_TARGET_REL".equals(fieldName) || "RQ_TARGET_RCYC".equals(fieldName)) { // hard-code the linked fields here if (fieldValue == null || fieldValue.trim().length() == 0) { req.clearListValuedField(fieldName); } else { req.setListValuedField(fieldName, fieldValue); } } else { req.setField(fieldName, fieldValue); } } catch (Exception e) { String message = "Exception while setting the value of field " + fieldName + " to " + fieldValue + ": " + e.getMessage(); log.error(message, e); throw new CCFRuntimeException(message, e); } } else { log.debug(fieldName); } if (!fieldName.equals(QCConfigHelper.QC_RQ_DEV_COMMENTS)) allFieldNames.add(fieldName); } // set the requirement type again here (why?) req.setTypeId(informalRequirementsType); req.post(); } catch (Exception e) { String reqId = null; if (req != null) { reqId = req.getId(); reqFactory.removeItem(reqId); req = null; } String message = "Exception while creating requirement " + reqId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } finally { if (versionControlSupported) { try { versionControl.checkIn("CCF CheckIn"); versionControl.safeRelease(); } catch (Exception e) { String message = "Failed to checkin requirement " + req.getId() + " again"; log.error(message, e); req.unlockObject(); throw new CCFRuntimeException(message, e); } } if (req != null) { req.unlockObject(); } reqFactory = null; } return new QCRequirement((Requirement) req); } /** * For a given transactionId, this returns whether it is a create or update * operation. * * @param qcc * @param txnId * @return */ /* * public boolean checkForCreate(IConnection qcc, int txnId) { Boolean check * = false; int newRc = 0; String sql = * "SELECT * FROM AUDIT_PROPERTIES WHERE AP_ACTION_ID= '" + txnId + "'"; * IRecordSet newRs = null; try { newRs = executeSQL(qcc, sql); if (newRs != * null) newRc = newRs.getRecordCount(); for (int newCnt = 0; newCnt < * newRc; newCnt++, newRs.next()) { String fieldName = * newRs.getFieldValueAsString("AP_FIELD_NAME"); String oldFieldValue = * null; if (!(fieldName.equals("BG_DESCRIPTION"))) oldFieldValue = * newRs.getFieldValueAsString("AP_OLD_VALUE"); else oldFieldValue = * newRs.getFieldValueAsString("AP_OLD_LONG_VALUE"); if * (fieldName.equals("BG_VTS") && (oldFieldValue == null || (oldFieldValue * != null && oldFieldValue .equals("")))) return true; } } finally { if * (newRs != null) { newRs.safeRelease(); newRs = null; } } return check; } */ public void deleteDefect(String id) { // Yet to implement } public IRecordSet getAuditPropertiesRecordSet(IConnection qcc, List<String> txnIds) { String sql; if (txnIds.size() > 1000 && qcc.isOracle()) { sql = getAuditPropertiesWithTuplesQuery(qcc, txnIds); } else { sql = getAuditPropertiesWithSimpleINClause(qcc, txnIds); } IRecordSet newRs = qcc.executeSQL(sql); return newRs; } public ArtifactState getChangedDefectToForce(IConnection qcc, String artifactId) { ArtifactState defectState = new ArtifactState(); String sql = "SELECT AU_ENTITY_ID, AU_ACTION_ID, AU_TIME FROM AUDIT_LOG WHERE AU_ENTITY_TYPE = 'BUG' AND AU_ENTITY_ID ='" + artifactId + "' AND AU_ACTION!='DELETE' AND AU_FATHER_ID = '-1' ORDER BY AU_ACTION_ID DESC"; log.debug(sql); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); if (rs != null) { String bugId = rs.getFieldValueAsString("AU_ENTITY_ID"); String actionIdStr = rs.getFieldValueAsString("AU_ACTION_ID"); int actionId = Integer.parseInt(actionIdStr); String actionDateStr = rs.getFieldValueAsString("AU_TIME"); Date actionDate = DateUtil.parseQCDate(actionDateStr); defectState.setArtifactId(bugId); defectState.setArtifactLastModifiedDate(actionDate); defectState.setArtifactVersion(actionId); } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } return defectState; } public ArtifactState getChangedRequirementToForce(IConnection qcc, String artifactId, String reqType) { ArtifactState defectState = new ArtifactState(); String sql = sqlQueryToRetrieveAndForceLatestRequirement(artifactId, reqType); log.debug(sql); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); if (rs != null) { String bugId = rs.getFieldValueAsString("AU_ENTITY_ID"); String actionIdStr = rs.getFieldValueAsString("AU_ACTION_ID"); int actionId = Integer.parseInt(actionIdStr); String actionDateStr = rs.getFieldValueAsString("AU_TIME"); Date actionDate = DateUtil.parseQCDate(actionDateStr); defectState.setArtifactId(bugId); defectState.setArtifactLastModifiedDate(actionDate); defectState.setArtifactVersion(actionId); } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } return defectState; } public String getConcatinatedCommentValue(String oldFieldValue, String fieldValue, String connectorUser, IConnection qcc) { String concatinatedFieldValue = null; java.util.Date date = new java.util.Date(); String currentDateString = DateUtil.formatQCDate(date); fieldValue = fieldValue.replaceAll("\t", " "); final String qcMajorVersion = qcc.getMajorVersion(); if (!StringUtils.isEmpty(oldFieldValue)) { oldFieldValue = this.stripStartAndEndTags(oldFieldValue); if (QC_VERSION_12.equals(qcMajorVersion)) { concatinatedFieldValue = QC11_FIRST_TAGS + oldFieldValue + String.format(QC_COMMENT_START + QC12_FONT_TAG + QC_COMMENT_END, connectorUser, currentDateString, fieldValue.replace(QC_BREAK, QC_LINE_BREAK)) + QC11_LAST_TAGS; } else if (QC_VERSION_11.equals(qcMajorVersion)) { concatinatedFieldValue = QC11_FIRST_TAGS + oldFieldValue + String.format(QC_COMMENT_START + QC11_FONT_TAG + QC_COMMENT_END, connectorUser, currentDateString, fieldValue.replace(QC_BREAK, QC_LINE_BREAK)) + QC11_LAST_TAGS; } else { concatinatedFieldValue = FIRST_TAGS + oldFieldValue + QC_BREAK + UNDERSCORE_STRING + QC_BREAK + QC_VERSION_OTHERS_START + connectorUser + ", " + currentDateString + ": " + QC_VERSION_OTHERS_END + fieldValue + LAST_TAGS; } } else { if (QC_VERSION_12.equals(qcMajorVersion)) { concatinatedFieldValue = QC11_FIRST_TAGS + String.format(QC12_INITIAL_COMMENT_START, connectorUser, currentDateString, fieldValue.replaceAll(QC_BREAK, QC_LINE_BREAK)) + QC11_LAST_TAGS; } else if (QC_VERSION_11.equals(qcMajorVersion)) { concatinatedFieldValue = QC11_FIRST_TAGS + String.format(QC11_FIRST_COMMENT_START, connectorUser, currentDateString, fieldValue.replaceAll(QC_BREAK, QC_LINE_BREAK)) + QC11_LAST_TAGS; } else { concatinatedFieldValue = FIRST_TAGS + QC_VERSION_OTHERS_START + connectorUser + ", " + currentDateString + ": " + QC_VERSION_OTHERS_END + fieldValue + LAST_TAGS; } } return concatinatedFieldValue; } // TODO review public List<IQCDefect> getDefectsWithIds(IConnection qcc, List<Integer> ids) { IBugFactory bf = qcc.getBugFactory(); IFilter filter = bf.getFilter(); List<IQCDefect> tasks = new ArrayList<IQCDefect>(); for (int i = 0; i < ids.size(); ++i) { filter.setFilter(QC_BUG_ID, ids.get(i).toString()); IFactoryList fl = filter.getNewList(); IBug bug = fl.getBug(1); QCDefect defect = new QCDefect((Bug) bug); tasks.add(defect); fl.safeRelease(); } filter.safeRelease(); bf = null; return tasks; } public IQCDefect[] getDefectsWithOtherSystemId(IConnection qcc, String otherSystemIdField, String otherSystemIdValue) { IBugFactory bugFactory = qcc.getBugFactory(); IFilter filter = bugFactory.getFilter(); IFactoryList factoryList; log.error("--------------"); log.error(otherSystemIdField); log.error(otherSystemIdValue); log.error("--------------"); filter.setFilter(otherSystemIdField, otherSystemIdValue); factoryList = filter.getNewList(); int factoryListCount = factoryList.getCount(); IQCDefect[] qcDefectArray = new IQCDefect[factoryListCount]; for (int i = 1; i <= factoryListCount; ++i) { IBug bug = factoryList.getBug(i); qcDefectArray[i - 1] = new QCDefect((Bug) bug); } filter.safeRelease(); return qcDefectArray; } /** * Gets the difference between the comment values of the previous and * current transaction pointed by actionId * * @param qcc * @param actionId * @return */ public String getDeltaOfCommentForDefects(IRecordSet newRs, IConnection qcc) { return getDeltaOfComment(newRs, QCConfigHelper.QC_BG_DEV_COMMENTS, qcc); } /** * Gets the difference between the comment values of the previous and * current transaction pointed by actionId * * @param qcc * @param actionId * @return */ public String getDeltaOfCommentForRequirements(IRecordSet newRs, IConnection qcc) { return getDeltaOfComment(newRs, QCConfigHelper.QC_RQ_DEV_COMMENTS, qcc); } /** * Returns the value of a particular field in the given GenericArtifact. * * @param individualGenericArtifact * @param fieldName * @return */ public String getIntegerValueFromGenericArtifactAsString(GenericArtifact individualGenericArtifact, String fieldName) { Integer intFieldValue = (Integer) individualGenericArtifact .getAllGenericArtifactFieldsWithSameFieldName(fieldName).get(0).getFieldValue(); String fieldValue = Integer.toString(intFieldValue.intValue()); return fieldValue; } /** * Return all defects modified between the given time range, in a map * */ public List<ArtifactState> getLatestChangedDefects(IConnection qcc, String transactionId) { int rc = 0; String sql = "SELECT AU_ENTITY_ID, AU_ACTION_ID, AU_TIME FROM AUDIT_LOG WHERE AU_ENTITY_TYPE = 'BUG' AND AU_ACTION_ID > '" + transactionId + "' AND AU_ACTION!='DELETE' AND AU_FATHER_ID = '-1' ORDER BY AU_ACTION_ID"; log.debug(sql); ArrayList<ArtifactState> changedDefects = new ArrayList<ArtifactState>(); HashMap<String, ArtifactState> artifactIdStateMap = new HashMap<String, ArtifactState>(); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); if (rs != null) rc = rs.getRecordCount(); for (int cnt = 0; cnt < rc; cnt++, rs.next()) { String bugId = rs.getFieldValueAsString("AU_ENTITY_ID"); String actionIdStr = rs.getFieldValueAsString("AU_ACTION_ID"); int actionId = Integer.parseInt(actionIdStr); String actionDateStr = rs.getFieldValueAsString("AU_TIME"); Date actionDate = DateUtil.parseQCDate(actionDateStr); if (artifactIdStateMap.containsKey(bugId)) { ArtifactState state = artifactIdStateMap.get(bugId); changedDefects.remove(state); state.setArtifactLastModifiedDate(actionDate); state.setArtifactVersion(actionId); changedDefects.add(state); } else { ArtifactState state = new ArtifactState(); state.setArtifactId(bugId); state.setArtifactLastModifiedDate(actionDate); state.setArtifactVersion(actionId); changedDefects.add(state); artifactIdStateMap.put(bugId, state); } } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } return changedDefects; } public List<ArtifactState> getLatestChangedRequirements(IConnection qcc, String transactionId, String technicalRequirementsId, boolean ignoreRequirementRootFolder) { int rc = 0; String sql = sqlQueryToRetrieveLatestRequirement(transactionId, technicalRequirementsId, ignoreRequirementRootFolder); log.debug(sql); ArrayList<ArtifactState> changedRequirements = new ArrayList<ArtifactState>(); HashMap<String, ArtifactState> artifactIdStateMap = new HashMap<String, ArtifactState>(); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); if (rs != null) rc = rs.getRecordCount(); for (int cnt = 0; cnt < rc; cnt++, rs.next()) { String reqId = rs.getFieldValueAsString("AU_ENTITY_ID"); String actionIdStr = rs.getFieldValueAsString("AU_ACTION_ID"); int actionId = Integer.parseInt(actionIdStr); String actionDateStr = rs.getFieldValueAsString("AU_TIME"); Date actionDate = DateUtil.parseQCDate(actionDateStr); if (artifactIdStateMap.containsKey(reqId)) { ArtifactState state = artifactIdStateMap.get(reqId); changedRequirements.remove(state); state.setArtifactLastModifiedDate(actionDate); state.setArtifactVersion(actionId); changedRequirements.add(state); } else { ArtifactState state = new ArtifactState(); state.setArtifactId(reqId); state.setArtifactLastModifiedDate(actionDate); state.setArtifactVersion(actionId); changedRequirements.add(state); artifactIdStateMap.put(reqId, state); } } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } return changedRequirements; } public String getOldFieldValue(IRecordSet newRs, String fieldName) { int newRc = newRs.getRecordCount(); String oldFieldValue = null; for (int newCnt = 0; newCnt < newRc; newCnt++, newRs.next()) { String fieldNameRs = newRs.getFieldValueAsString("AP_FIELD_NAME"); if (fieldNameRs.equals(fieldName)) { oldFieldValue = newRs.getFieldValueAsString("AP_OLD_VALUE"); } } return oldFieldValue; } /** * Gets the value of the field in a suitable data type. * */ public String getProperFieldValue(GenericArtifactField thisField, String targetSystemTimezone) { String fieldValue = null; GenericArtifactField.FieldValueTypeValue fieldValueTypeValue = thisField.getFieldValueType(); switch (fieldValueTypeValue) { case DATE: { GregorianCalendar gcal = (GregorianCalendar) thisField.getFieldValue(); if (gcal != null) { Date targetTimezoneDate = gcal.getTime(); if (DateUtil.isAbsoluteDateInTimezone(targetTimezoneDate, DateUtil.GMT_TIME_ZONE_STRING)) { targetTimezoneDate = DateUtil.convertGMTToTimezoneAbsoluteDate(targetTimezoneDate, TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT)); } fieldValue = DateUtil.formatQCDate(targetTimezoneDate); } break; } case DATETIME: { Date targetTimezoneDate = (Date) thisField.getFieldValue(); if (targetTimezoneDate != null) { fieldValue = DateUtil.formatQCDate(targetTimezoneDate); } break; } } return fieldValue; } // public String stripLastTags(String oldFieldValue) { // if (oldFieldValue.endsWith(LAST_TAGS)) { // return oldFieldValue.substring(0, oldFieldValue.length() - // LAST_TAGS.length()); // } // else { // return oldFieldValue; // } // } public List<String> getTransactionIdsInRangeForDefects(IConnection qcc, int entityId, int syncInfoTxnId, int actionId, String connectorUser, String resyncUser) { List<String> listOfTxnIds = new ArrayList<String>(); connectorUser = connectorUser == null ? " " : connectorUser; // had to change this line because MS SQL does not accept empty strings in SQL queries resyncUser = resyncUser == null ? connectorUser : resyncUser; String sql = "SELECT AU_ACTION_ID FROM AUDIT_LOG WHERE AU_ACTION_ID > '" + syncInfoTxnId + "' AND AU_ACTION_ID <= '" + actionId + "' AND AU_ACTION!='DELETE' AND AU_ENTITY_TYPE='BUG' AND AU_USER!='" + connectorUser + "' AND AU_USER!='" + resyncUser + "' AND AU_FATHER_ID='-1' AND AU_ENTITY_ID='" + entityId + "'"; IRecordSet newRs = null; try { newRs = qcc.executeSQL(sql); log.debug("The SQL query in getTransactionIdsInRangeForDefects::" + sql); int newRc = newRs.getRecordCount(); for (int newCnt = 0; newCnt < newRc; newCnt++, newRs.next()) { String fieldValue = newRs.getFieldValueAsString("AU_ACTION_ID"); listOfTxnIds.add(fieldValue); } } finally { if (newRs != null) { newRs.safeRelease(); newRs = null; } } return listOfTxnIds; } public List<String> getTransactionIdsInRangeForRequirements(IConnection qcc, int entityId, int syncInfoTxnId, int actionId, String connectorUser, String resyncUser) { List<String> listOfTxnIds = new ArrayList<String>(); connectorUser = connectorUser == null ? " " : connectorUser; // had to change this line because MS SQL does not accept empty strings in SQL queries resyncUser = resyncUser == null ? connectorUser : resyncUser; String sql = "SELECT AU_ACTION_ID FROM AUDIT_LOG WHERE AU_ACTION_ID > '" + syncInfoTxnId + "' AND AU_ACTION_ID <= '" + actionId + "' AND AU_ACTION!='DELETE' AND AU_ENTITY_TYPE='REQ' AND AU_USER!='" + connectorUser + "' AND AU_USER!='" + resyncUser + "' AND AU_FATHER_ID='-1' AND AU_ENTITY_ID='" + entityId + "'"; IRecordSet newRs = null; try { newRs = qcc.executeSQL(sql); log.debug("The SQL query in getTransactionIdsInRangeForRequirements::" + sql); int newRc = newRs.getRecordCount(); for (int newCnt = 0; newCnt < newRc; newCnt++, newRs.next()) { String fieldValue = newRs.getFieldValueAsString("AU_ACTION_ID"); listOfTxnIds.add(fieldValue); } } finally { if (newRs != null) { newRs.safeRelease(); newRs = null; } } return listOfTxnIds; } /** * Obtains the artifactAction based on the date at which that defect was * created and the lastReadTime synchronization parameter. * * @param entityId * The defectId for which the search has to be made in QC * @param actionId * The transactionId at which it needs to be determined if the * defect is a create or update. * @param qcc * The Connection object * @param latestDefectArtifact * The GenericArtifact into which the artifactAction is populated * after it is determined. * @param lastReadTime * This is synchronization parameter used to compare with the * defect created date and find out the artifactAction. * @return GenericArtifact Updated artifact */ /* * public GenericArtifact getArtifactActionForDefects( GenericArtifact * latestDefectArtifact, IConnection qcc, String syncInfoTransactionId, * String actionId, int entityId, String lastReadTime) { * List<GenericArtifactField> genArtifactFields = latestDefectArtifact * .getAllGenericArtifactFieldsWithSameFieldName("BG_VTS"); // Date * lastReadDate = DateUtil.parse(lastReadTime); // Date createdOn = * qcGAHelper.getDefectCreatedDate(qcc, entityId); if (genArtifactFields != * null && genArtifactFields.get(0) != null) { String bgVts = * qcGAHelper.findVtsFromQC(qcc, Integer .parseInt(actionId), entityId); * Date newBgVts = DateUtil.parseQCDate(bgVts); * genArtifactFields.get(0).setFieldValue(newBgVts); String lastModifiedDate * = DateUtil.format(newBgVts); * latestDefectArtifact.setSourceArtifactLastModifiedDate(lastModifiedDate); * } return latestDefectArtifact; } */ public boolean isUseAlternativeFieldName() { return useAlternativeFieldName; } /** * Orders the values of the incoming HashMap according to its keys. * * @param HashMap * This hashMap contains the transactionIds as values indexed by * their defectIds. * @return List<String> This list of strings is the ordering of the keys of * the incoming HashMaps, which are the defectIds, according to the * order of their transactionIds * */ public List<String> orderByLatestTransactionIds(Map<String, String> defectIdTransactionIdMap) { List<String> mapKeys = new ArrayList<String>(defectIdTransactionIdMap.keySet()); List<String> mapValues = new ArrayList<String>(defectIdTransactionIdMap.values()); defectIdTransactionIdMap.clear(); TreeSet<String> sortedSet = new TreeSet<String>(mapValues); Object[] sortedArray = sortedSet.toArray(); int size = sortedArray.length; for (int i = 0; i < size; i++) { defectIdTransactionIdMap.put(mapKeys.get(mapValues.indexOf(sortedArray[i])), (String) sortedArray[i]); } List<String> orderedDefectList = new ArrayList<String>(defectIdTransactionIdMap.keySet()); return orderedDefectList; } public void setUseAlternativeFieldName(boolean useAlternativeFieldName) { this.useAlternativeFieldName = useAlternativeFieldName; } /** * Updates the defect identified by the incoming bugId in QC * * @param qcc * The Connection object * @param bugId * The ID of the defect to be updated * @param List * <GenericArtifactField> The values of each fields of the defect * that need to be updated on the old values. * @param connectorUser * The connectorUser name used while updating the comments * @param ignoreLocks * @param genericArtifact */ public void updateDefect(IConnection qcc, String bugId, List<GenericArtifactField> allFields, String connectorUser, String targetSystemTimezone, boolean preserveSemanticallyUnchangedHTMLFieldValues, boolean ignoreLocks, GenericArtifact genericArtifact) { IBugFactory bugFactory = null; IBug bug = null; boolean lockedBug = false; boolean movedLock = false; boolean wasPartialUpdate = false; boolean shouldPerformCompleteUpdate = genericArtifact .getArtifactMode() != GenericArtifact.ArtifactModeValue.CHANGEDFIELDSONLY; Map<String, String> qcDefectSchemaMetadataCache = null; if (isUseAlternativeFieldName()) { GenericArtifact genericArtifactQCSchemaFields = QCConfigHelper.getSchemaFieldsForDefect(qcc); qcDefectSchemaMetadataCache = getQCSchemaMetadataMap(genericArtifactQCSchemaFields); } try { bugFactory = qcc.getBugFactory(); bug = bugFactory.getItem(bugId); if (ignoreLocks) { String lockOwner = getLockOwner(qcc, bugId, true); if (!StringUtils.isEmpty(lockOwner) && !qcc.getUsername().equalsIgnoreCase(lockOwner)) { if (!shouldPerformCompleteUpdate) { // comments that have previously been quarantined for // later processing. throw new DefectAlreadyLockedException(String.format( "Could not perform partial update, because defect '%s' is still locked / locked again by user '%s'.", bugId, lockOwner)); } else { log.info("Going to take ownership of the lock for defect " + bugId + " currently held by " + lockOwner); moveLock(qcc, bugId, true); movedLock = true; } } } bug.lockObject(); lockedBug = true; Set<String> allFieldNames = new HashSet<String>(); String fieldValue = null; for (GenericArtifactField thisField : allFields) { String fieldName = thisField.getFieldName(); if (isUseAlternativeFieldName()) { fieldName = getTechnicalNameForQCField(qcDefectSchemaMetadataCache, fieldName); } if (!shouldPerformCompleteUpdate && !QCConfigHelper.QC_BG_DEV_COMMENTS.equals(fieldName)) { // we already updated the other fields before and are only // interested in the comments this time. continue; } if (thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATE) || thisField .getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATETIME)) { fieldValue = getProperFieldValue(thisField, targetSystemTimezone); } else { fieldValue = (String) thisField.getFieldValue(); } try { if (fieldName.equals(QCConfigHelper.QC_BG_DEV_COMMENTS)) { String oldFieldValue = bug.getFieldAsString(fieldName); if ((!StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue) && !oldFieldValue.equals(fieldValue)) || (StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue))) { fieldValue = getConcatinatedCommentValue(oldFieldValue, fieldValue, connectorUser, qcc); } if (!movedLock) { bug.setField(fieldName, fieldValue); } else { wasPartialUpdate = true; } } else if (!(allFieldNames.contains(fieldName)) && !(fieldName.equals(QC_BUG_ID) || fieldName.equals(QC_BUG_VER_STAMP) || fieldName.equals(QC_BG_ATTACHMENT) || fieldName.equals(QC_BG_VTS) || fieldName.endsWith(QCConfigHelper.HUMAN_READABLE_SUFFIX))) { // only handle every field once // if there was a multi select field, we already // concatenated all its values in the field value of its // first occurrence if (preserveSemanticallyUnchangedHTMLFieldValues && !StringUtils.isEmpty(fieldValue) && fieldValue.startsWith(QCConfigHelper.HTMLSTRING_PREFIX)) { String oldFieldValue = bug.getFieldAsString(fieldName); String strippedOldValue = GATransformerUtil.stripHTML(oldFieldValue).replaceAll("\\s", ""); String strippedNewValue = GATransformerUtil.stripHTML(fieldValue).replaceAll("\\s", ""); if (StringUtils.isEmpty(oldFieldValue) || !strippedNewValue.equals(strippedOldValue)) { bug.setField(fieldName, fieldValue); } else { log.info("skipping update of field '" + fieldName + "', because only fomatting has changed."); } } else { bug.setField(fieldName, fieldValue); } allFieldNames.add(fieldName); } } catch (ComFailException e) { String message = "Exception while setting the value of field " + fieldName + " to " + fieldValue + ": " + e.getMessage(); log.error(message, e); throw new CCFRuntimeException(message, e); } } bug.post(); } catch (DefectAlreadyLockedException e) { /* * do not try to restore a moved lock here, because this condition * would only occur if another user re-locked the artifact before we * could acquire it after stealing it. */ if (!shouldPerformCompleteUpdate && ignoreLocks) { throw new CCFRuntimeException(e.getMessage(), e); } else { String message = "Attempt to lock the defect with id " + bugId + " failed."; String lockOwner = getLockOwner(qcc, bugId, true); if (StringUtils.isEmpty(lockOwner)) { message += " Could not find out the user who locked it."; } else { message += " Defect has been locked by user " + lockOwner; } throw new CCFRuntimeException(message, e); } } catch (ComFailException e) { bug.undo(); String message = "ComFailException while updating the defect with id " + bugId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } catch (Exception e) { bug.undo(); String message = "Exception while updating the defect with id " + bugId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } finally { if (bug != null) { if (lockedBug) { bug.unlockObject(); } bug.safeRelease(); } if (movedLock) { log.info("Giving lock of defect " + bugId + " back to its original owner..."); restoreLock(qcc, bugId, true); if (wasPartialUpdate) { // mark defect as only partially updated, so later // components can // react appropriately. log.info("Since defect " + bugId + " is currently locked and shipment contains at least one comment, only fields will be updated now and comments will be stored in the hospital ..."); genericArtifact.setErrorCode(GenericArtifact.ERROR_OBJECT_LOCKED); genericArtifact.setArtifactMode(ArtifactModeValue.CHANGEDFIELDSONLY); } } } } /** * Updates the requirement identified by the incoming requirement in QC * * @param qcc * The Connection object * @param requirementId * The ID of the requirement to be updated * @param List * <GenericArtifactField> The values of each fields of the * requirement that need to be updated on the old values. * @param connectorUser * The connectorUser name used while updating the comments * @param targetParentArtifactId * @param preserveSemanticallyUnchangedHTMLFieldValues * @param ignoreLocks * @param genericArtifact * */ public void updateRequirement(IConnection qcc, String requirementId, List<GenericArtifactField> allFields, String connectorUser, String targetSystemTimezone, String targetParentArtifactId, boolean preserveSemanticallyUnchangedHTMLFieldValues, boolean ignoreLocks, GenericArtifact genericArtifact) { IRequirementsFactory reqFactory = null; IRequirement req = null; boolean lockedReq = false; boolean movedLock = false; boolean wasPartialUpdate = false; boolean shouldPerformCompleteUpdate = genericArtifact .getArtifactMode() != GenericArtifact.ArtifactModeValue.CHANGEDFIELDSONLY; Map<String, String> qcRequirementSchemaMetadataCache = null; if (isUseAlternativeFieldName()) { String technicalReleaseTypeId = QCConnectionFactory .extractTechnicalRequirementsType(genericArtifact.getTargetRepositoryId(), qcc); GenericArtifact genericArtifactQCSchemaFields = QCConfigHelper.getSchemaFieldsForRequirement(qcc, technicalReleaseTypeId); qcRequirementSchemaMetadataCache = getQCSchemaMetadataMap(genericArtifactQCSchemaFields); } IVersionControl versionControl = null; boolean versionControlSupported = false; try { reqFactory = qcc.getRequirementsFactory(); req = reqFactory.getItem(requirementId); versionControl = req.getVersionControlObject(); if (versionControl != null) { versionControlSupported = ccfCheckoutReq(qcc, req, versionControl); } if (ignoreLocks) { String lockOwner = getLockOwner(qcc, requirementId, false); if (!StringUtils.isEmpty(lockOwner) && !qcc.getUsername().equalsIgnoreCase(lockOwner)) { if (!shouldPerformCompleteUpdate) { // comments that have previously been quarantined for // later processing. throw new DefectAlreadyLockedException(String.format( "Could not perform partial update, because requirement '%s' is still locked / locked again by user '%s'.", requirementId, lockOwner)); } else { log.info("Going to take ownership of the lock for requirement " + requirementId + " currently held by " + lockOwner); moveLock(qcc, requirementId, false); movedLock = true; } } } req.lockObject(); lockedReq = true; Set<String> allFieldNames = new HashSet<String>(); String fieldValue = null; for (GenericArtifactField thisField : allFields) { String fieldName = thisField.getFieldName(); if (isUseAlternativeFieldName()) { fieldName = getTechnicalNameForQCField(qcRequirementSchemaMetadataCache, fieldName); } if (!shouldPerformCompleteUpdate && !QCConfigHelper.QC_RQ_DEV_COMMENTS.equals(fieldName)) { // we already updated the other fields before and are only // interested in the comments this time. continue; } if (thisField.getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATE) || thisField .getFieldValueType().equals(GenericArtifactField.FieldValueTypeValue.DATETIME)) { fieldValue = getProperFieldValue(thisField, targetSystemTimezone); } else { fieldValue = (String) thisField.getFieldValue(); } try { if (fieldName.equals(QCConfigHelper.QC_RQ_DEV_COMMENTS)) { String oldFieldValue = req.getFieldAsString(fieldName); if ((!StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue) && !oldFieldValue.equals(fieldValue)) || (StringUtils.isEmpty(oldFieldValue) && !StringUtils.isEmpty(fieldValue))) { fieldValue = getConcatinatedCommentValue(oldFieldValue, fieldValue, connectorUser, qcc); } if (!movedLock) { req.setField(fieldName, fieldValue); } else { wasPartialUpdate = true; } } else if (!(allFieldNames.contains(fieldName)) && !(fieldName.equals(QC_REQ_ID) || fieldName.equals(QC_RQ_ATTACHMENT) || fieldName.equals(QC_RQ_VTS) || fieldName.endsWith(QCConfigHelper.HUMAN_READABLE_SUFFIX))) { // only handle every field once // if there was a multi select field, we already // concatenated all its values in the field value of its // first occurrence if (preserveSemanticallyUnchangedHTMLFieldValues && !StringUtils.isEmpty(fieldValue) && fieldValue.startsWith(QCConfigHelper.HTMLSTRING_PREFIX)) { String oldFieldValue = req.getFieldAsString(fieldName); String strippedOldValue = GATransformerUtil.stripHTML(oldFieldValue).replaceAll("\\s", ""); String strippedNewValue = GATransformerUtil.stripHTML(fieldValue).replaceAll("\\s", ""); if (StringUtils.isEmpty(oldFieldValue) || !strippedNewValue.equals(strippedOldValue)) { req.setField(fieldName, fieldValue); } else { log.info("skipping update of field '" + fieldName + "', because only fomatting has changed."); } } else if ("RQ_TARGET_REL".equals(fieldName) || "RQ_TARGET_RCYC".equals(fieldName)) { // hard-code the linked fields here if (fieldValue == null || fieldValue.trim().length() == 0) { req.clearListValuedField(fieldName); } else { req.setListValuedField(fieldName, fieldValue); } } else { req.setField(fieldName, fieldValue); } allFieldNames.add(fieldName); } } catch (ComFailException e) { String message = "Exception while setting the value of field " + fieldName + " to " + fieldValue + ": " + e.getMessage(); log.error(message, e); throw new CCFRuntimeException(message, e); } } req.post(); String parentId = req.getParentId(); // move to other parent if necessary if (targetParentArtifactId != null && !targetParentArtifactId.equals(GenericArtifact.VALUE_UNKNOWN) && !targetParentArtifactId.equals(parentId)) { if (targetParentArtifactId.equals(GenericArtifact.VALUE_NONE)) { if (!"-1".equals(parentId) && !"0".equals(parentId)) { // we assume that requirement 0 is the top level // requirement req.move(0, 1); } } else { req.move(Integer.parseInt(targetParentArtifactId), 1); } } } catch (DefectAlreadyLockedException e) { /* * do not try to restore a moved lock here, because this condition * would only occur if another user re-locked the artifact before we * could acquire it after stealing it. */ if (!shouldPerformCompleteUpdate && ignoreLocks) { throw new CCFRuntimeException(e.getMessage(), e); } else { String message = "Attempt to lock the requirement with id " + requirementId + " failed."; String lockOwner = getLockOwner(qcc, requirementId, false); if (StringUtils.isEmpty(lockOwner)) { message += " Could not find out the user who locked it."; } else { message += " Requirement has been locked by user " + lockOwner; } throw new CCFRuntimeException(message, e); } } catch (ComFailException e) { req.undo(); String message = "ComFailException while updating the requirement with id " + requirementId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } catch (Exception e) { req.undo(); String message = "Exception while updating the requirement with id " + requirementId; log.error(message, e); throw new CCFRuntimeException(message + ": " + e.getMessage(), e); } finally { if (versionControlSupported) { try { versionControl.checkIn("CCF CheckIn"); versionControl.safeRelease(); } catch (Exception e) { String message = "Failed to checkin requirement " + req.getId() + " again"; log.error(message, e); if (lockedReq) { req.unlockObject(); } if (movedLock) { restoreLock(qcc, requirementId, false); if (wasPartialUpdate) { // mark requirement as only partially updated, so // later components can // react appropriately. genericArtifact.setErrorCode(GenericArtifact.ERROR_OBJECT_LOCKED); genericArtifact.setArtifactMode(ArtifactModeValue.CHANGEDFIELDSONLY); } } throw new CCFRuntimeException(message, e); } } if (req != null) { if (lockedReq) { req.unlockObject(); } req.safeRelease(); } if (movedLock) { log.info("Giving lock of requirement " + requirementId + " back to its original owner..."); restoreLock(qcc, requirementId, false); if (wasPartialUpdate) { // mark requirement as only partially updated, so later // components can // react appropriately. log.info("Since requirement " + requirementId + " is currently locked and shipment contains at least one comment, only fields will be updated now and comments will be stored in the hospital ..."); genericArtifact.setErrorCode(GenericArtifact.ERROR_OBJECT_LOCKED); genericArtifact.setArtifactMode(ArtifactModeValue.CHANGEDFIELDSONLY); } } } } private String artifactTypeString(boolean isDefect) { return isDefect ? "BUG" : "REQ"; } private boolean ccfCheckoutReq(IConnection qcc, IRequirement req, IVersionControl versionControl) { boolean versionControlSupported = false; try { versionControlSupported = versionControl.checkOut("CCF Checkout"); } catch (ComFailException e) { // check whether we have already checked out this // requirement if (qcc.getUsername().equalsIgnoreCase(req.getFieldAsString("RQ_VC_CHECKOUT_USER_NAME"))) { log.warn("Requirement " + req.getId() + " has been already checked out by connector user " + qcc.getUsername() + " so we still proceed ..."); } else { String message = "Requirement " + req.getId() + " has been checked out by " + req.getFieldAsString("RQ_VC_CHECKOUT_USER_NAME") + " on " + req.getFieldAsDate("RQ_VC_CHECKOUT_DATE") + " at " + req.getFieldAsString("RQ_VC_CHECKOUT_TIME") + " with version number " + req.getFieldAsInt("RQ_VC_VERSION_NUMBER"); log.error(message, e); throw new CCFRuntimeException(message, e); } } return versionControlSupported; } /** * @param commentHTML * @param qcMajorVersion * @return */ private String cleanUpComment(String commentHTML, String qcMajorVersion) { // remove the first comment separator. // QC11 introduces a <span> element after the <font> element. String res = commentHTML.replaceFirst(QC_COMMENT_SEPARATOR, ""); // remove junk that causes Jericho to insert a newline as the first // char in the comment, causing "hanging comments" in TF. // Unlike previous versions, the first comment of an artifact in QC11 doesn't // start with a <br/> element, but with an "almost empty" div: if (res.startsWith(QC_FIRST_COMMENT_PREFIX)) { res = res.substring(QC_FIRST_COMMENT_PREFIX.length()); } else if (QC_VERSION_11.equals(qcMajorVersion)) { // remove the first <br/> to avoid "hanging comments" after Jericho conversion res = res.replaceFirst(QC_FIRST_BREAK, ""); } //New comment now start with an underscore string //so removing it to get the comment value. if (res.startsWith(QC_UNDERSCORE_PREFIX)) { res = res.replaceFirst(QC_UNDERSCORE_PREFIX, ""); } // replace empty lines by lines containing a non-breaking space // to prevent Jericho from removing the first empty line. // this applies only to QC11, QC10 uses different HTML that // doesn't exhibit this problem in the first place. res = res.replaceAll(QC_TAG_WITH_EMPTY_LINE, QC_LINES_WITH_NON_BREAKING_SPACE); return FIRST_TAGS + res + LAST_TAGS; } private String configureRequirementsType(List<GenericArtifactField> allFields, String informalRequirementsType) { if (REQUIREMENT_TYPE_ALL.equals(informalRequirementsType)) { for (GenericArtifactField gaField : allFields) { String fieldName = gaField.getFieldName(); if (QCHandler.QC_REQ_TYPE_ID.equals(fieldName)) { informalRequirementsType = (String) gaField.getFieldValue(); break; } continue; } } return informalRequirementsType; } private void deleteStaleMovedLock(IConnection qcc, String artifactId, boolean isDefect) { int newArtifactId = -(Integer.parseInt(artifactId) + 1); String sql = String.format("DELETE FROM LOCKS WHERE LK_OBJECT_KEY = '%s' AND LK_OBJECT_TYPE = '%s'", newArtifactId, artifactTypeString(isDefect)); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); } finally { if (rs != null) { rs.safeRelease(); rs = null; } } } private String getAuditPropertiesWithSimpleINClause(IConnection qcc, List<String> txnIds) { StringBuilder sql = new StringBuilder("SELECT * FROM AUDIT_PROPERTIES WHERE AP_ACTION_ID in ("); int len = txnIds.size(); for (int cnt = 0; cnt < len; cnt++) { if (cnt != (len - 1)) sql.append("'" + txnIds.get(cnt) + "',"); else sql.append("'" + txnIds.get(cnt) + "'"); } sql.append(") ORDER BY AP_PROPERTY_ID ASC "); log.debug("New SQL in getAuditPropertiesWithSimpleINClause is:" + sql); return sql.toString(); } private String getAuditPropertiesWithTuplesQuery(IConnection qcc, List<String> txnIds) { StringBuilder sql = new StringBuilder("SELECT * FROM AUDIT_PROPERTIES WHERE (1,AP_ACTION_ID) in ("); int len = txnIds.size(); for (int cnt = 0; cnt < len; cnt++) { if (cnt != (len - 1)) sql.append("(1,'" + txnIds.get(cnt) + "'),"); else sql.append("(1,'" + txnIds.get(cnt) + "')"); } sql.append(") ORDER BY AP_PROPERTY_ID ASC "); log.debug("New SQL in getAuditPropertiesWithTuplesQuery is:" + sql); return sql.toString(); } /** * @param newRs * @param filterFieldName * @return */ private String getDeltaOfComment(IRecordSet newRs, String filterFieldName, IConnection qcc) { String deltaComment = ""; String newFieldValue = null; String emptyString = ""; int newRc = newRs.getRecordCount(); String qcMajorVersion = qcc.getMajorVersion(); for (int newCnt = 0; newCnt < newRc; newCnt++, newRs.next()) { String fieldName = newRs.getFieldValueAsString("AP_FIELD_NAME"); if (fieldName.equals(filterFieldName)) { String oldFieldValue = newRs.getFieldValueAsString("AP_OLD_LONG_VALUE"); newFieldValue = newRs.getFieldValueAsString("AP_NEW_LONG_VALUE"); if (!StringUtils.isEmpty(newFieldValue) && !StringUtils.isEmpty(oldFieldValue)) { String strippedOldValue = stripStartAndEndTags(oldFieldValue); String strippedNewValue = stripStartAndEndTags(newFieldValue); String delta = ""; if (QC_VERSION_12.equals(qcMajorVersion)) { //Extract text values from html tags String trimmedOldValue = JerichoUtils.htmlToText(strippedOldValue); String trimmedNewValue = JerichoUtils.htmlToText(strippedNewValue); //Get the new comment value from the extracted text String newComment = trimmedNewValue.substring(trimmedOldValue.length()); // All characters before first '\r' will be skipped. //This is to get the new comment values alone and omit // the extra characters carry forwarded when switching between clients. delta = StringUtils.substringAfter(newComment, QC12_LINE_BREAK); //Encode back html to entity references to preserve tags around the <fullname> in the QC comment format //which is validated during transformation deltaComment += com.collabnet.ccf.core.utils.StringUtils .encodeHTMLToEntityReferences(delta); } else { if (newFieldValue.length() > oldFieldValue.length()) { if (QC_VERSION_11.equals(qcMajorVersion)) { delta = strippedNewValue.substring(strippedOldValue.length()); /* * QC11 inserts line breaks into the comment * values, which causes the new value to be too * long after a comment was synched e.g. from * TF. Thus, the delta contains some characters * at the beginning which belong to the previous * comment. Here, we cut these off in order to * prevent the extraneous chars from being * synched back to the other system. XXX: this * will break if a very long comment or very * many comments are synched, so that the start * of the delta moves so far back that the * "extraneous chars" contain the entire * previous comment. */ final String commentPrefix = QC_COMMENT_PREFIX; final int offset = delta.indexOf(commentPrefix); if (offset > 0) { delta = delta.substring(offset); } } else { delta = strippedNewValue.substring(strippedOldValue.length()); } deltaComment += delta; } else if (newFieldValue.length() == oldFieldValue.length()) { log.warn("QC comments not changed"); } else { log.warn("New comment is smaller than old comment"); } } } else { if (!StringUtils.isEmpty(newFieldValue)) { deltaComment = stripStartAndEndTags(newFieldValue); } } } } if (StringUtils.isEmpty(newFieldValue)) return emptyString; else { return cleanUpComment(deltaComment, qcMajorVersion); } } private String getLockOwner(IConnection qcc, String artifactId, boolean isDefect) { // retrieving user who locked the bug IRecordSet rs = null; try { rs = qcc.executeSQL( String.format("SELECT LK_USER FROM LOCKS WHERE LK_OBJECT_KEY = '%s' AND LK_OBJECT_TYPE = '%s'", artifactId, artifactTypeString(isDefect))); if (rs.getRecordCount() != 1) { return null; } else { String userName = rs.getFieldValueAsString("LK_USER"); return userName; } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } } private Map<String, String> getQCSchemaMetadataMap(GenericArtifact genericArtifactQCSchemaFields) { Map<String, String> metadataMap = new HashMap<String, String>(); List<GenericArtifactField> allGenericFields = genericArtifactQCSchemaFields.getAllGenericArtifactFields(); for (GenericArtifactField field : allGenericFields) { metadataMap.put(field.getAlternativeFieldName(), field.getFieldName()); } return metadataMap; } private String getTechnicalNameForQCField(Map<String, String> qcDefectSchemaMetadataCache, String fieldName) { String technicalName = qcDefectSchemaMetadataCache.get(fieldName); if (technicalName == null) { technicalName = fieldName; } return technicalName; } private void moveLock(IConnection qcc, String artifactId, boolean isDefect) { deleteStaleMovedLock(qcc, artifactId, isDefect); int newLockId = -(Integer.parseInt(artifactId) + 1); String sql = String.format( "UPDATE LOCKS SET LK_OBJECT_KEY='%s' WHERE LK_OBJECT_KEY='%s' AND LK_OBJECT_TYPE='%s'", newLockId, artifactId, artifactTypeString(isDefect)); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); } finally { if (rs != null) { rs.safeRelease(); rs = null; } } } private void restoreLock(IConnection qcc, String artifactId, boolean isDefect) { int currentLockId = -(Integer.parseInt(artifactId) + 1); String sql = String.format( "UPDATE LOCKS SET LK_OBJECT_KEY='%s' WHERE LK_OBJECT_KEY='%s' AND LK_OBJECT_TYPE='%s'", artifactId, currentLockId, artifactTypeString(isDefect)); IRecordSet rs = null; try { rs = qcc.executeSQL(sql); } catch (Exception e) { deleteStaleMovedLock(qcc, artifactId, isDefect); } finally { if (rs != null) { rs.safeRelease(); rs = null; } } } private String sqlQueryToRetrieveAndForceLatestRequirement(String artifactId, String technicalRequirementsId) { StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append( "SELECT AL.AU_ENTITY_ID AS AU_ENTITY_ID, AL.AU_ACTION_ID AS AU_ACTION_ID, AL.AU_TIME AS AU_TIME FROM AUDIT_LOG AL, REQ WHERE AL.AU_ENTITY_TYPE = 'REQ' AND AU_ENTITY_ID = '") .append(artifactId).append("' AND AL.AU_ACTION!='DELETE' AND AL.AU_FATHER_ID = '-1'"); if (!QCHandler.REQUIREMENT_TYPE_ALL.equals(technicalRequirementsId)) { queryBuilder.append(" AND REQ.RQ_TYPE_ID = '").append(technicalRequirementsId).append("'"); } queryBuilder.append(" AND AL.AU_ENTITY_ID = REQ.RQ_REQ_ID ORDER BY AL.AU_ACTION_ID DESC"); return queryBuilder.toString(); } private String sqlQueryToRetrieveLatestRequirement(String transactionId, String technicalRequirementsId, boolean ignoreRequirementRootFolder) { StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append( "SELECT AL.AU_ENTITY_ID AS AU_ENTITY_ID, AL.AU_ACTION_ID AS AU_ACTION_ID, AL.AU_TIME AS AU_TIME FROM AUDIT_LOG AL, REQ WHERE AL.AU_ENTITY_TYPE = 'REQ' AND AU_ACTION_ID > '") .append(transactionId).append("' AND AL.AU_ACTION!='DELETE' AND AL.AU_FATHER_ID = '-1'"); if (ignoreRequirementRootFolder) { queryBuilder.append(" AND REQ.RQ_REQ_ID != '0'"); } if (!QCHandler.REQUIREMENT_TYPE_ALL.equals(technicalRequirementsId)) { queryBuilder.append(" AND REQ.RQ_TYPE_ID = '").append(technicalRequirementsId).append("'"); } queryBuilder.append(" AND AL.AU_ENTITY_ID = REQ.RQ_REQ_ID ORDER BY AL.AU_ACTION_ID"); return queryBuilder.toString(); } private String stripStartAndEndTags(String fieldValue) { if (StringUtils.isEmpty(fieldValue)) { return ""; } return fieldValue.replaceAll(QC_START__HTML_TAGS, "").replaceAll(QC_END_HTML_TAGS, ""); } public static String getRequirementTypeTechnicalId(IConnection qcc, String requirementTypeName) { IRecordSet rs = null; try { rs = qcc.executeSQL("SELECT TPR_TYPE_ID FROM REQ_TYPE WHERE TPR_NAME = '" + requirementTypeName + "'"); if (rs.getRecordCount() != 1) { throw new CCFRuntimeException( "Could not retrieve technical id for requirements type " + requirementTypeName); } else { return rs.getFieldValueAsString("TPR_TYPE_ID"); } } finally { if (rs != null) { rs.safeRelease(); rs = null; } } } }