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

org.sonar.server.debt.DebtRulesXMLImporter Maven / Gradle / Ivy

There is a newer version: 7.2.1
Show newest version
/*
 * SonarQube
 * Copyright (C) 2009-2018 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.server.debt;

import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.codehaus.stax2.XMLInputFactory2;
import org.codehaus.staxmate.SMInputFactory;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;

import static com.google.common.collect.Lists.newArrayList;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static javax.xml.stream.XMLInputFactory.IS_COALESCING;
import static javax.xml.stream.XMLInputFactory.IS_NAMESPACE_AWARE;
import static javax.xml.stream.XMLInputFactory.IS_VALIDATING;
import static javax.xml.stream.XMLInputFactory.SUPPORT_DTD;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.sonar.api.server.debt.DebtRemediationFunction.Type.CONSTANT_ISSUE;
import static org.sonar.api.server.debt.DebtRemediationFunction.Type.LINEAR;
import static org.sonar.api.utils.Duration.MINUTE;
import static org.sonar.server.debt.DebtModelXMLExporter.CHARACTERISTIC;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_COEFFICIENT;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_FUNCTION;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_KEY;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_OFFSET;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_TEXT_VALUE;
import static org.sonar.server.debt.DebtModelXMLExporter.PROPERTY_VALUE;
import static org.sonar.server.debt.DebtModelXMLExporter.REPOSITORY_KEY;
import static org.sonar.server.debt.DebtModelXMLExporter.RULE_KEY;

/**
 * Import rules debt definitions from an XML
 */
@ServerSide
@ComputeEngineSide
public class DebtRulesXMLImporter {

  public List importXML(String xml, ValidationMessages validationMessages) {
    return importXML(new StringReader(xml), validationMessages);
  }

  public List importXML(Reader xml, ValidationMessages validationMessages) {
    List ruleDebts = newArrayList();
    try {
      SMInputFactory inputFactory = initStax();
      SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);

      // advance to 
      cursor.advance();
      SMInputCursor rootCursor = cursor.childElementCursor(CHARACTERISTIC);
      while (rootCursor.getNext() != null) {
        process(ruleDebts, validationMessages, rootCursor);
      }

      cursor.getStreamReader().closeCompletely();
    } catch (XMLStreamException e) {
      throw new IllegalStateException("XML is not valid", e);
    }
    return ruleDebts;
  }

  private static SMInputFactory initStax() {
    XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
    xmlFactory.setProperty(IS_COALESCING, TRUE);
    xmlFactory.setProperty(IS_NAMESPACE_AWARE, FALSE);
    xmlFactory.setProperty(SUPPORT_DTD, FALSE);
    xmlFactory.setProperty(IS_VALIDATING, FALSE);
    return new SMInputFactory(xmlFactory);
  }

  private static void process(List ruleDebts,
    ValidationMessages validationMessages, SMInputCursor chcCursor) throws XMLStreamException {
    SMInputCursor cursor = chcCursor.childElementCursor();
    while (cursor.getNext() != null) {
      String node = cursor.getLocalName();
      if (StringUtils.equals(node, CHARACTERISTIC)) {
        process(ruleDebts, validationMessages, cursor);
      } else if (StringUtils.equals(node, REPOSITORY_KEY)) {
        RuleDebt ruleDebt = processRule(validationMessages, cursor);
        if (ruleDebt != null) {
          ruleDebts.add(ruleDebt);
        }
      }
    }
  }

  @CheckForNull
  private static RuleDebt processRule(ValidationMessages validationMessages, SMInputCursor cursor) throws XMLStreamException {
    String ruleRepositoryKey = cursor.collectDescendantText().trim();
    String ruleKey = null;
    Properties properties = new Properties();
    while (cursor.getNext() != null) {
      String node = cursor.getLocalName();
      if (StringUtils.equals(node, PROPERTY)) {
        properties.add(processProperty(validationMessages, cursor));
      } else if (StringUtils.equals(node, RULE_KEY)) {
        ruleKey = cursor.collectDescendantText().trim();
      }
    }
    if (isNotBlank(ruleRepositoryKey) && isNotBlank(ruleKey)) {
      return createRule(RuleKey.of(ruleRepositoryKey, ruleKey), properties, validationMessages);
    }
    return null;
  }

  private static Property processProperty(ValidationMessages validationMessages, SMInputCursor cursor) throws XMLStreamException {
    SMInputCursor c = cursor.childElementCursor();
    String key = null;
    int value = 0;
    String textValue = null;
    while (c.getNext() != null) {
      String node = c.getLocalName();
      if (StringUtils.equals(node, PROPERTY_KEY)) {
        key = c.collectDescendantText().trim();

      } else if (StringUtils.equals(node, PROPERTY_VALUE)) {
        String s = c.collectDescendantText().trim();
        try {
          Double valueDouble = NumberUtils.createDouble(s);
          value = valueDouble.intValue();
        } catch (NumberFormatException ex) {
          validationMessages.addErrorText(String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key));
        }
      } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
        textValue = c.collectDescendantText().trim();
        textValue = "mn".equals(textValue) ? MINUTE : textValue;
      }
    }
    return new Property(key, value, textValue);
  }

  @CheckForNull
  private static RuleDebt createRule(RuleKey ruleKey, Properties properties, ValidationMessages validationMessages) {
    Property function = properties.function();
    Property coefficientProperty = properties.coefficient();
    String coefficient = coefficientProperty == null ? null : coefficientProperty.toDuration();
    Property offsetProperty = properties.offset();
    String offset = offsetProperty == null ? null : offsetProperty.toDuration();
    if (function != null && (coefficient != null || offset != null)) {
      return createRuleDebt(ruleKey, function.getTextValue(), coefficient, offset, validationMessages);
    }
    return null;
  }

  @CheckForNull
  private static RuleDebt createRuleDebt(RuleKey ruleKey, String function, @Nullable String coefficient, @Nullable String offset, ValidationMessages validationMessages) {
    if ("constant_resource".equals(function)) {
      validationMessages.addWarningText(String.format("Constant/file function is no longer used, technical debt definitions on '%s' are ignored.", ruleKey));
      return null;
    }
    if ("linear_threshold".equals(function) && coefficient != null) {
      validationMessages.addWarningText(String.format("Linear with threshold function is no longer used, remediation function of '%s' is replaced by linear.", ruleKey));
      return createRuleDebt(ruleKey, LINEAR.name(), coefficient, null, validationMessages);
    }
    if (CONSTANT_ISSUE.name().equalsIgnoreCase(function) && coefficient != null && offset == null) {
      return createRuleDebt(ruleKey, CONSTANT_ISSUE.name(), null, coefficient, validationMessages);
    }
    return new RuleDebt().setRuleKey(ruleKey).setFunction(function.toUpperCase()).setCoefficient(coefficient).setOffset(offset);
  }

  private static class Properties {
    List list;

    public Properties() {
      this.list = newArrayList();
    }

    public Properties add(Property property) {
      this.list.add(property);
      return this;
    }

    public Property function() {
      return find(PROPERTY_FUNCTION);
    }

    public Property coefficient() {
      return find(PROPERTY_COEFFICIENT);
    }

    public Property offset() {
      return find(PROPERTY_OFFSET);
    }

    private Property find(String key) {
      return Iterables.find(list, new PropertyMatchKey(key), null);
    }
  }

  private static class Property {
    String key;
    int value;
    String textValue;

    private Property(String key, int value, String textValue) {
      this.key = key;
      this.value = value;
      this.textValue = textValue;
    }

    private String getKey() {
      return key;
    }

    private int getValue() {
      return value;
    }

    private String getTextValue() {
      return textValue;
    }

    @CheckForNull
    public String toDuration() {
      if (key != null && getValue() > 0) {
        String duration = Integer.toString(getValue());
        duration += !Strings.isNullOrEmpty(getTextValue()) ? getTextValue() : Duration.DAY;
        return duration;
      }
      return null;
    }
  }

  private static class PropertyMatchKey implements Predicate {
    private final String key;

    public PropertyMatchKey(String key) {
      this.key = key;
    }

    @Override
    public boolean apply(@Nonnull Property input) {
      return input.getKey().equals(key);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy