org.sonar.server.debt.DebtRulesXMLImporter.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.debt.DebtRulesXMLImporter.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2017 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<RuleDebt> importXML(String xml, ValidationMessages validationMessages) {
        return importXML(new StringReader(xml), validationMessages);
    }

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

            // advance to <sqale>
            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<RuleDebt> 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<Property> 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<Property> {
        private final String key;

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

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

}