From 8d294104c34f96f7e9e670f26483a3a0eaf81d87 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Thu, 14 Apr 2011 12:58:19 +0200 Subject: [PATCH] [SONAR-2347] Close a review when its corresponding violation does not exist anymore --- .../core/sensors/ReviewsDecorator.java | 117 ++-- .../ViolationPersisterDecorator.java | 380 ++++++------- .../core/sensors/ReviewsDecoratorTest.java | 58 ++ .../sensors/ReviewsDecoratorTest/fixture.xml | 53 ++ .../sonar/batch/index/ViolationPersister.java | 169 +++--- .../sonar/jpa/session/JpaDatabaseSession.java | 520 +++++++++--------- .../sonar/api/database/DatabaseSession.java | 158 +++--- 7 files changed, 807 insertions(+), 648 deletions(-) create mode 100644 plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsDecoratorTest.java create mode 100644 plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsDecoratorTest/fixture.xml diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsDecorator.java index 040dc72dd72..1faef7c4f66 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsDecorator.java @@ -1,46 +1,71 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.plugins.core.sensors; - -import org.sonar.api.batch.Decorator; -import org.sonar.api.batch.DecoratorContext; -import org.sonar.api.batch.DependedUpon; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; - -/** - * Decorator that currently only closes a review when its corresponding violation has been fixed. - */ -@DependedUpon("ViolationPersisterDecorator") -public class ReviewsDecorator implements Decorator { - - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - public void decorate(Resource resource, DecoratorContext context) { - // - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } -} +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.sensors; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.Query; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.ResourcePersister; + +/** + * Decorator that currently only closes a review when its corresponding violation has been fixed. + */ +@DependsUpon("ViolationPersisterDecorator") +public class ReviewsDecorator implements Decorator { + + private static final Logger LOG = LoggerFactory.getLogger(ReviewsDecorator.class); + + private ResourcePersister resourcePersister; + private DatabaseSession databaseSession; + + public ReviewsDecorator(ResourcePersister resourcePersister, DatabaseSession databaseSession) { + this.resourcePersister = resourcePersister; + this.databaseSession = databaseSession; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(Resource resource, DecoratorContext context) { + Snapshot currentSnapshot = resourcePersister.getSnapshot(resource); + if (currentSnapshot != null) { + int resourceId = currentSnapshot.getResourceId(); + int snapshotId = currentSnapshot.getId(); + Query query = databaseSession.createNativeQuery("UPDATE reviews SET status='closed' " + "WHERE resource_id = " + resourceId + + " AND rule_failure_permanent_id NOT IN " + "(SELECT permanent_id FROM rule_failures WHERE snapshot_id = " + snapshotId + ")"); + int rowUpdated = query.executeUpdate(); + LOG.debug("- {} reviews set to 'closed' on resource #{}", rowUpdated, resourceId); + databaseSession.commit(); + } + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecorator.java index 8360475d550..455ec647f94 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecorator.java @@ -1,186 +1,196 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.plugins.core.timemachine; - -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.*; -import org.sonar.api.database.model.RuleFailureModel; -import org.sonar.api.database.model.SnapshotSource; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.Violation; -import org.sonar.batch.components.PastViolationsLoader; -import org.sonar.batch.index.ViolationPersister; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -@DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION) -@DependedUpon("ViolationPersisterDecorator") /* temporary workaround - see NewViolationsDecorator */ -public class ViolationPersisterDecorator implements Decorator { - - /** - * Those chars would be ignored during generation of checksums. - */ - private static final String SPACE_CHARS = "\t\n\r "; - - private RuleFinder ruleFinder; - private PastViolationsLoader pastViolationsLoader; - private ViolationPersister violationPersister; - - List checksums = Lists.newArrayList(); - - public ViolationPersisterDecorator(RuleFinder ruleFinder, PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) { - this.ruleFinder = ruleFinder; - this.pastViolationsLoader = pastViolationsLoader; - this.violationPersister = violationPersister; - } - - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - public void decorate(Resource resource, DecoratorContext context) { - if (context.getViolations().isEmpty()) { - return; - } - // Load past violations - List pastViolations = pastViolationsLoader.getPastViolations(resource); - // Load current source and calculate checksums - checksums = getChecksums(pastViolationsLoader.getSource(resource)); - // Save violations - compareWithPastViolations(context, pastViolations); - // Clear cache - checksums.clear(); - } - - private void compareWithPastViolations(DecoratorContext context, List pastViolations) { - Multimap pastViolationsByRule = LinkedHashMultimap.create(); - for (RuleFailureModel pastViolation : pastViolations) { - Rule rule = ruleFinder.findById(pastViolation.getRuleId()); - pastViolationsByRule.put(rule, pastViolation); - } - // for each violation, search equivalent past violation - for (Violation violation : context.getViolations()) { - RuleFailureModel pastViolation = selectPastViolation(violation, pastViolationsByRule); - if (pastViolation != null) { - // remove violation, since would be updated and shouldn't affect other violations anymore - pastViolationsByRule.remove(violation.getRule(), pastViolation); - } - String checksum = getChecksumForLine(checksums, violation.getLineId()); - violationPersister.saveViolation(context.getProject(), violation, pastViolation, checksum); - } - } - - /** - * @return checksums, never null - */ - private List getChecksums(SnapshotSource source) { - return source == null || source.getData() == null ? Collections.emptyList() : getChecksums(source.getData()); - } - - /** - * @param data can't be null - */ - static List getChecksums(String data) { - String[] lines = data.split("\r?\n|\r", -1); - List result = Lists.newArrayList(); - for (String line : lines) { - result.add(getChecksum(line)); - } - return result; - } - - static String getChecksum(String line) { - String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, ""); - return DigestUtils.md5Hex(reducedLine); - } - - /** - * @return checksum or null if checksum not exists for line - */ - private String getChecksumForLine(List checksums, Integer line) { - if (line == null || line < 1 || line > checksums.size()) { - return null; - } - return checksums.get(line - 1); - } - - /** - * Search for past violation. - */ - RuleFailureModel selectPastViolation(Violation violation, Multimap pastViolationsByRule) { - Collection pastViolations = pastViolationsByRule.get(violation.getRule()); - if (pastViolations == null || pastViolations.isEmpty()) { - // skip violation, if there is no past violations with same rule - return null; - } - String dbFormattedMessage = RuleFailureModel.abbreviateMessage(violation.getMessage()); - RuleFailureModel found = selectPastViolationUsingLine(violation, dbFormattedMessage, pastViolations); - if (found == null) { - found = selectPastViolationUsingChecksum(violation, dbFormattedMessage, pastViolations); - } - return found; - } - - /** - * Search for past violation with same message and line. - */ - private RuleFailureModel selectPastViolationUsingLine(Violation violation, String dbFormattedMessage, Collection pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { - if (ObjectUtils.equals(violation.getLineId(), pastViolation.getLine()) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) { - return pastViolation; - } - } - return null; - } - - /** - * Search for past violation with same message and checksum. - */ - private RuleFailureModel selectPastViolationUsingChecksum(Violation violation, String dbFormattedMessage, Collection pastViolations) { - String checksum = getChecksumForLine(checksums, violation.getLineId()); - // skip violation, which not attached to line - if (checksum == null) { - return null; - } - for (RuleFailureModel pastViolation : pastViolations) { - String pastChecksum = pastViolation.getChecksum(); - if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) { - return pastViolation; - } - } - return null; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.timemachine; + +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; +import org.codehaus.plexus.util.StringInputStream; +import org.sonar.api.batch.*; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.database.model.SnapshotSource; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.Violation; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.components.PastViolationsLoader; +import org.sonar.batch.index.ViolationPersister; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION) +@DependedUpon("ViolationPersisterDecorator") /* temporary workaround - see NewViolationsDecorator */ +public class ViolationPersisterDecorator implements Decorator { + + /** + * Those chars would be ignored during generation of checksums. + */ + private static final String SPACE_CHARS = "\t\n\r "; + + private RuleFinder ruleFinder; + private PastViolationsLoader pastViolationsLoader; + private ViolationPersister violationPersister; + + List checksums = Lists.newArrayList(); + + public ViolationPersisterDecorator(RuleFinder ruleFinder, PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) { + this.ruleFinder = ruleFinder; + this.pastViolationsLoader = pastViolationsLoader; + this.violationPersister = violationPersister; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(Resource resource, DecoratorContext context) { + if (context.getViolations().isEmpty()) { + return; + } + // Load past violations + List pastViolations = pastViolationsLoader.getPastViolations(resource); + // Load current source and calculate checksums + checksums = getChecksums(pastViolationsLoader.getSource(resource)); + // Save violations + compareWithPastViolations(context, pastViolations); + // Clear cache + checksums.clear(); + } + + private void compareWithPastViolations(DecoratorContext context, List pastViolations) { + Multimap pastViolationsByRule = LinkedHashMultimap.create(); + for (RuleFailureModel pastViolation : pastViolations) { + Rule rule = ruleFinder.findById(pastViolation.getRuleId()); + pastViolationsByRule.put(rule, pastViolation); + } + // for each violation, search equivalent past violation + for (Violation violation : context.getViolations()) { + RuleFailureModel pastViolation = selectPastViolation(violation, pastViolationsByRule); + if (pastViolation != null) { + // remove violation, since would be updated and shouldn't affect other violations anymore + pastViolationsByRule.remove(violation.getRule(), pastViolation); + } + String checksum = getChecksumForLine(checksums, violation.getLineId()); + violationPersister.saveViolation(context.getProject(), violation, pastViolation, checksum); + } + violationPersister.commit(); + } + + /** + * @return checksums, never null + */ + private List getChecksums(SnapshotSource source) { + return source == null || source.getData() == null ? Collections.emptyList() : getChecksums(source.getData()); + } + + static List getChecksums(String data) { + List result = Lists.newArrayList(); + StringInputStream stream = new StringInputStream(data); + try { + List lines = IOUtils.readLines(stream); + for (String line : lines) { + result.add(getChecksum(line)); + } + } catch (IOException e) { + throw new SonarException("Unable to calculate checksums", e); + + } finally { + IOUtils.closeQuietly(stream); + } + return result; + } + + static String getChecksum(String line) { + String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, ""); + return DigestUtils.md5Hex(reducedLine); + } + + /** + * @return checksum or null if checksum not exists for line + */ + private String getChecksumForLine(List checksums, Integer line) { + if (line == null || line < 1 || line > checksums.size()) { + return null; + } + return checksums.get(line - 1); + } + + /** + * Search for past violation. + */ + RuleFailureModel selectPastViolation(Violation violation, Multimap pastViolationsByRule) { + Collection pastViolations = pastViolationsByRule.get(violation.getRule()); + if (pastViolations==null || pastViolations.isEmpty()) { + // skip violation, if there is no past violations with same rule + return null; + } + String dbFormattedMessage = RuleFailureModel.abbreviateMessage(violation.getMessage()); + RuleFailureModel found = selectPastViolationUsingLine(violation, dbFormattedMessage, pastViolations); + if (found == null) { + found = selectPastViolationUsingChecksum(violation, dbFormattedMessage, pastViolations); + } + return found; + } + + /** + * Search for past violation with same message and line. + */ + private RuleFailureModel selectPastViolationUsingLine(Violation violation, String dbFormattedMessage, Collection pastViolations) { + for (RuleFailureModel pastViolation : pastViolations) { + if (ObjectUtils.equals(violation.getLineId(), pastViolation.getLine()) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) { + return pastViolation; + } + } + return null; + } + + /** + * Search for past violation with same message and checksum. + */ + private RuleFailureModel selectPastViolationUsingChecksum(Violation violation, String dbFormattedMessage, Collection pastViolations) { + String checksum = getChecksumForLine(checksums, violation.getLineId()); + // skip violation, which not attached to line + if (checksum == null) { + return null; + } + for (RuleFailureModel pastViolation : pastViolations) { + String pastChecksum = pastViolation.getChecksum(); + if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) { + return pastViolation; + } + } + return null; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsDecoratorTest.java new file mode 100644 index 00000000000..1fd4da1748a --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsDecoratorTest.java @@ -0,0 +1,58 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.sensors; + +import org.junit.Ignore; +import org.junit.Test; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Resource; +import org.sonar.api.utils.DateUtils; +import org.sonar.batch.components.PastSnapshot; +import org.sonar.batch.components.TimeMachineConfiguration; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.jpa.test.AbstractDbUnitTestCase; + +import java.text.ParseException; +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ReviewsDecoratorTest extends AbstractDbUnitTestCase { + + @Test + @Ignore("DBUnit needs Hibernate mapping...") + public void shouldSaveConfigurationInSnapshotsTable() throws ParseException { + setupData("fixture"); + + File resource = new File("Foo"); + Snapshot snapshot = new Snapshot(); + snapshot.setId(666); + snapshot.setResourceId(111); + ResourcePersister persister = mock(ResourcePersister.class); + when(persister.getSnapshot(resource)).thenReturn(snapshot); + + ReviewsDecorator reviewsDecorator = new ReviewsDecorator(persister, getSession()); + reviewsDecorator.decorate(resource, null); + + //checkTables("shouldSaveConfigurationInSnapshotsTable", "snapshots"); + } +} diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsDecoratorTest/fixture.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsDecoratorTest/fixture.xml new file mode 100644 index 00000000000..24cf1cfb55d --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsDecoratorTest/fixture.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java index 64d2c85c2d7..7782e961922 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java @@ -1,82 +1,87 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.batch.index; - -import org.sonar.api.database.DatabaseSession; -import org.sonar.api.database.model.RuleFailureModel; -import org.sonar.api.database.model.Snapshot; -import org.sonar.api.resources.Project; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.Violation; - -public final class ViolationPersister { - - private DatabaseSession session; - private ResourcePersister resourcePersister; - private RuleFinder ruleFinder; - - public ViolationPersister(DatabaseSession session, ResourcePersister resourcePersister, RuleFinder ruleFinder) { - this.session = session; - this.resourcePersister = resourcePersister; - this.ruleFinder = ruleFinder; - } - - void saveViolation(Project project, Violation violation) { - saveViolation(project, violation, null, null); - } - - public void saveViolation(Project project, Violation violation, RuleFailureModel pastViolation, String checksum) { - Snapshot snapshot = resourcePersister.saveResource(project, violation.getResource()); - - RuleFailureModel model = createModel(violation); - if (pastViolation!=null) { - model.setCreatedAt(pastViolation.getCreatedAt()); - model.setPermanentId(pastViolation.getPermanentId()); - - } else { - // avoid plugins setting date - model.setCreatedAt(snapshot.getCreatedAt()); - } - model.setSnapshotId(snapshot.getId()); - model.setChecksum(checksum); - session.save(model); - - if (model.getPermanentId()==null) { - model.setPermanentId(model.getId()); - session.save(model); - } - - // the following fields can have been changed - violation.setMessage(model.getMessage());// the message can be changed in the class RuleFailure (truncate + trim) - violation.setCreatedAt(model.getCreatedAt()); - } - - private RuleFailureModel createModel(Violation violation) { - RuleFailureModel model = new RuleFailureModel(); - Rule rule = ruleFinder.findByKey(violation.getRule().getRepositoryKey(), violation.getRule().getKey()); - model.setRuleId(rule.getId()); - model.setPriority(violation.getSeverity()); - model.setLine(violation.getLineId()); - model.setMessage(violation.getMessage()); - model.setCost(violation.getCost()); - return model; - } -} +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.index; + +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.Project; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.Violation; + +public final class ViolationPersister { + + private DatabaseSession session; + private ResourcePersister resourcePersister; + private RuleFinder ruleFinder; + + public ViolationPersister(DatabaseSession session, ResourcePersister resourcePersister, RuleFinder ruleFinder) { + this.session = session; + this.resourcePersister = resourcePersister; + this.ruleFinder = ruleFinder; + } + + void saveViolation(Project project, Violation violation) { + saveViolation(project, violation, null, null); + } + + public void saveViolation(Project project, Violation violation, RuleFailureModel pastViolation, String checksum) { + Snapshot snapshot = resourcePersister.saveResource(project, violation.getResource()); + + RuleFailureModel model = createModel(violation); + if (pastViolation!=null) { + model.setCreatedAt(pastViolation.getCreatedAt()); + model.setPermanentId(pastViolation.getPermanentId()); + + } else { + // avoid plugins setting date + model.setCreatedAt(snapshot.getCreatedAt()); + } + model.setSnapshotId(snapshot.getId()); + model.setChecksum(checksum); + session.save(model); + + if (model.getPermanentId()==null) { + model.setPermanentId(model.getId()); + session.save(model); + } + + // the following fields can have been changed + violation.setMessage(model.getMessage());// the message can be changed in the class RuleFailure (truncate + trim) + violation.setCreatedAt(model.getCreatedAt()); + } + + public void commit() { + session.commit(); + } + + private RuleFailureModel createModel(Violation violation) { + RuleFailureModel model = new RuleFailureModel(); + Rule rule = ruleFinder.findByKey(violation.getRule().getRepositoryKey(), violation.getRule().getKey()); + model.setRuleId(rule.getId()); + model.setPriority(violation.getSeverity()); + model.setLine(violation.getLineId()); + model.setMessage(violation.getMessage()); + model.setCost(violation.getCost()); + return model; + } + +} diff --git a/sonar-core/src/main/java/org/sonar/jpa/session/JpaDatabaseSession.java b/sonar-core/src/main/java/org/sonar/jpa/session/JpaDatabaseSession.java index 09fb4894c88..6533b5ae5cf 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/session/JpaDatabaseSession.java +++ b/sonar-core/src/main/java/org/sonar/jpa/session/JpaDatabaseSession.java @@ -1,257 +1,263 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.jpa.session; - -import org.apache.commons.lang.StringUtils; -import org.sonar.api.database.DatabaseSession; - -import java.util.*; - -import javax.persistence.EntityManager; -import javax.persistence.NonUniqueResultException; -import javax.persistence.PersistenceException; -import javax.persistence.Query; - -public class JpaDatabaseSession extends DatabaseSession { - - private final DatabaseConnector connector; - private EntityManager entityManager = null; - private int index = 0; - private boolean inTransaction = false; - - public JpaDatabaseSession(DatabaseConnector connector) { - this.connector = connector; - } - - /** - * Note that usage of this method is discouraged, because it allows to construct and execute queries without additional exception handling, - * which done in methods of this class. - */ - public EntityManager getEntityManager() { - return entityManager; - } - - public void start() { - entityManager = connector.createEntityManager(); - index = 0; - } - - public void stop() { - commit(); - if (entityManager != null && entityManager.isOpen()) { - entityManager.close(); - entityManager = null; - } - } - - public void commit() { - if (entityManager != null && inTransaction) { - if (entityManager.isOpen()) { - if (entityManager.getTransaction().getRollbackOnly()) { - entityManager.getTransaction().rollback(); - } else { - entityManager.getTransaction().commit(); - } - entityManager.clear(); - index = 0; - } - inTransaction = false; - } - } - - public void rollback() { - if (entityManager != null && inTransaction) { - entityManager.getTransaction().rollback(); - inTransaction = false; - } - } - - public T save(T model) { - startTransaction(); - internalSave(model, true); - return model; - } - - public Object saveWithoutFlush(Object model) { - startTransaction(); - internalSave(model, false); - return model; - } - - public boolean contains(Object model) { - startTransaction(); - return entityManager.contains(model); - } - - public void save(Object... models) { - startTransaction(); - for (Object model : models) { - save(model); - } - } - - private void internalSave(Object model, boolean flushIfNeeded) { - try { - entityManager.persist(model); - } catch (PersistenceException e) { - /* - * See http://jira.codehaus.org/browse/SONAR-2234 - * In some cases Hibernate can throw exceptions without meaningful information about context, so we improve them here. - */ - throw new PersistenceException("Unable to persist : " + model, e); - } - if (flushIfNeeded && (++index % BATCH_SIZE == 0)) { - commit(); - } - } - - public Object merge(Object model) { - startTransaction(); - return entityManager.merge(model); - } - - public void remove(Object model) { - startTransaction(); - entityManager.remove(model); - if (++index % BATCH_SIZE == 0) { - commit(); - } - } - - public void removeWithoutFlush(Object model) { - startTransaction(); - entityManager.remove(model); - } - - public T reattach(Class entityClass, Object primaryKey) { - startTransaction(); - return entityManager.getReference(entityClass, primaryKey); - } - - private void startTransaction() { - if (!inTransaction) { - entityManager.getTransaction().begin(); - inTransaction = true; - } - } - - /** - * Note that not recommended to directly execute {@link Query#getSingleResult()}, because it will bypass exception handling, - * which done in {@link #getSingleResult(Query, Object)}. - */ - public Query createQuery(String hql) { - startTransaction(); - return entityManager.createQuery(hql); - } - - /** - * @return the result or defaultValue, if not found - * @throws NonUniqueResultException if more than one result - */ - public T getSingleResult(Query query, T defaultValue) { - /* - * See http://jira.codehaus.org/browse/SONAR-2225 - * By default Hibernate throws NonUniqueResultException without meaningful information about context, - * so we improve it here by adding all results in error message. - * Note that in some rare situations we can receive too many results, which may lead to OOME, - * but actually it will mean that database is corrupted as we don't expect more than one result - * and in fact org.hibernate.ejb.QueryImpl#getSingleResult() anyway does loading of several results under the hood. - */ - List result = query.getResultList(); - - if (result.size() == 1) { - return result.get(0); - - } else if (result.isEmpty()) { - return defaultValue; - - } else { - Set uniqueResult = new HashSet(result); - if (uniqueResult.size() > 1) { - throw new NonUniqueResultException("Expected single result, but got : " + result.toString()); - } else { - return uniqueResult.iterator().next(); - } - } - } - - public T getEntity(Class entityClass, Object id) { - startTransaction(); - return getEntityManager().find(entityClass, id); - } - - /** - * @return the result or null, if not found - * @throws NonUniqueResultException if more than one result - */ - public T getSingleResult(Class entityClass, Object... criterias) { - try { - return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null); - - } catch (NonUniqueResultException ex) { - NonUniqueResultException e = new NonUniqueResultException("Expected single result for entitiy " + entityClass.getSimpleName() - + " with criterias : " + StringUtils.join(criterias, ",")); - e.initCause(ex); - throw e; - } - } - - public List getResults(Class entityClass, Object... criterias) { - return getQueryForCriterias(entityClass, true, criterias).getResultList(); - } - - public List getResults(Class entityClass) { - return getQueryForCriterias(entityClass, false, null).getResultList(); - } - - private Query getQueryForCriterias(Class entityClass, boolean raiseError, Object... criterias) { - if (criterias == null && raiseError) { - throw new IllegalStateException("criterias parameter must be provided"); - } - startTransaction(); - StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o"); - if (criterias != null) { - hql.append(" WHERE "); - Map mappedCriterias = new HashMap(); - for (int i = 0; i < criterias.length; i += 2) { - mappedCriterias.put((String) criterias[i], criterias[i + 1]); - } - buildCriteriasHQL(hql, mappedCriterias); - Query query = getEntityManager().createQuery(hql.toString()); - - for (Map.Entry entry : mappedCriterias.entrySet()) { - query.setParameter(entry.getKey(), entry.getValue()); - } - return query; - } - return getEntityManager().createQuery(hql.toString()); - } - - private void buildCriteriasHQL(StringBuilder hql, Map mappedCriterias) { - for (Iterator i = mappedCriterias.keySet().iterator(); i.hasNext();) { - String criteria = i.next(); - hql.append("o.").append(criteria).append("=:").append(criteria); - if (i.hasNext()) { - hql.append(" AND "); - } - } - } - -} +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.jpa.session; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.database.DatabaseSession; + +import java.util.*; + +import javax.persistence.EntityManager; +import javax.persistence.NonUniqueResultException; +import javax.persistence.PersistenceException; +import javax.persistence.Query; + +public class JpaDatabaseSession extends DatabaseSession { + + private final DatabaseConnector connector; + private EntityManager entityManager = null; + private int index = 0; + private boolean inTransaction = false; + + public JpaDatabaseSession(DatabaseConnector connector) { + this.connector = connector; + } + + /** + * Note that usage of this method is discouraged, because it allows to construct and execute queries without additional exception handling, + * which done in methods of this class. + */ + public EntityManager getEntityManager() { + return entityManager; + } + + public void start() { + entityManager = connector.createEntityManager(); + index = 0; + } + + public void stop() { + commit(); + if (entityManager != null && entityManager.isOpen()) { + entityManager.close(); + entityManager = null; + } + } + + public void commit() { + if (entityManager != null && inTransaction) { + if (entityManager.isOpen()) { + if (entityManager.getTransaction().getRollbackOnly()) { + entityManager.getTransaction().rollback(); + } else { + entityManager.getTransaction().commit(); + } + entityManager.clear(); + index = 0; + } + inTransaction = false; + } + } + + public void rollback() { + if (entityManager != null && inTransaction) { + entityManager.getTransaction().rollback(); + inTransaction = false; + } + } + + public T save(T model) { + startTransaction(); + internalSave(model, true); + return model; + } + + public Object saveWithoutFlush(Object model) { + startTransaction(); + internalSave(model, false); + return model; + } + + public boolean contains(Object model) { + startTransaction(); + return entityManager.contains(model); + } + + public void save(Object... models) { + startTransaction(); + for (Object model : models) { + save(model); + } + } + + private void internalSave(Object model, boolean flushIfNeeded) { + try { + entityManager.persist(model); + } catch (PersistenceException e) { + /* + * See http://jira.codehaus.org/browse/SONAR-2234 + * In some cases Hibernate can throw exceptions without meaningful information about context, so we improve them here. + */ + throw new PersistenceException("Unable to persist : " + model, e); + } + if (flushIfNeeded && (++index % BATCH_SIZE == 0)) { + commit(); + } + } + + public Object merge(Object model) { + startTransaction(); + return entityManager.merge(model); + } + + public void remove(Object model) { + startTransaction(); + entityManager.remove(model); + if (++index % BATCH_SIZE == 0) { + commit(); + } + } + + public void removeWithoutFlush(Object model) { + startTransaction(); + entityManager.remove(model); + } + + public T reattach(Class entityClass, Object primaryKey) { + startTransaction(); + return entityManager.getReference(entityClass, primaryKey); + } + + private void startTransaction() { + if (!inTransaction) { + entityManager.getTransaction().begin(); + inTransaction = true; + } + } + + /** + * Note that not recommended to directly execute {@link Query#getSingleResult()}, because it will bypass exception handling, + * which done in {@link #getSingleResult(Query, Object)}. + */ + public Query createQuery(String hql) { + startTransaction(); + return entityManager.createQuery(hql); + } + + @Override + public Query createNativeQuery(String sql) { + startTransaction(); + return entityManager.createNativeQuery(sql); + } + + /** + * @return the result or defaultValue, if not found + * @throws NonUniqueResultException if more than one result + */ + public T getSingleResult(Query query, T defaultValue) { + /* + * See http://jira.codehaus.org/browse/SONAR-2225 + * By default Hibernate throws NonUniqueResultException without meaningful information about context, + * so we improve it here by adding all results in error message. + * Note that in some rare situations we can receive too many results, which may lead to OOME, + * but actually it will mean that database is corrupted as we don't expect more than one result + * and in fact org.hibernate.ejb.QueryImpl#getSingleResult() anyway does loading of several results under the hood. + */ + List result = query.getResultList(); + + if (result.size() == 1) { + return result.get(0); + + } else if (result.isEmpty()) { + return defaultValue; + + } else { + Set uniqueResult = new HashSet(result); + if (uniqueResult.size() > 1) { + throw new NonUniqueResultException("Expected single result, but got : " + result.toString()); + } else { + return uniqueResult.iterator().next(); + } + } + } + + public T getEntity(Class entityClass, Object id) { + startTransaction(); + return getEntityManager().find(entityClass, id); + } + + /** + * @return the result or null, if not found + * @throws NonUniqueResultException if more than one result + */ + public T getSingleResult(Class entityClass, Object... criterias) { + try { + return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null); + + } catch (NonUniqueResultException ex) { + NonUniqueResultException e = new NonUniqueResultException("Expected single result for entitiy " + entityClass.getSimpleName() + + " with criterias : " + StringUtils.join(criterias, ",")); + e.initCause(ex); + throw e; + } + } + + public List getResults(Class entityClass, Object... criterias) { + return getQueryForCriterias(entityClass, true, criterias).getResultList(); + } + + public List getResults(Class entityClass) { + return getQueryForCriterias(entityClass, false, null).getResultList(); + } + + private Query getQueryForCriterias(Class entityClass, boolean raiseError, Object... criterias) { + if (criterias == null && raiseError) { + throw new IllegalStateException("criterias parameter must be provided"); + } + startTransaction(); + StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o"); + if (criterias != null) { + hql.append(" WHERE "); + Map mappedCriterias = new HashMap(); + for (int i = 0; i < criterias.length; i += 2) { + mappedCriterias.put((String) criterias[i], criterias[i + 1]); + } + buildCriteriasHQL(hql, mappedCriterias); + Query query = getEntityManager().createQuery(hql.toString()); + + for (Map.Entry entry : mappedCriterias.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + return query; + } + return getEntityManager().createQuery(hql.toString()); + } + + private void buildCriteriasHQL(StringBuilder hql, Map mappedCriterias) { + for (Iterator i = mappedCriterias.keySet().iterator(); i.hasNext();) { + String criteria = i.next(); + hql.append("o.").append(criteria).append("=:").append(criteria); + if (i.hasNext()) { + hql.append(" AND "); + } + } + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/database/DatabaseSession.java b/sonar-plugin-api/src/main/java/org/sonar/api/database/DatabaseSession.java index e36a22e8e3f..dc416bc5257 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/database/DatabaseSession.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/database/DatabaseSession.java @@ -1,78 +1,80 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.api.database; - -import org.sonar.api.BatchComponent; - -import javax.persistence.EntityManager; -import javax.persistence.Query; -import java.util.List; - -/** - * This component should not accessible from plugin API - * - * @since 1.10 - */ -public abstract class DatabaseSession implements BatchComponent { - - - // IMPORTANT : this value must be the same than the property - // hibernate.jdbc.batch_size from /META-INF/persistence.xml (module sonar-database) - public static final int BATCH_SIZE = 30; - - - public abstract EntityManager getEntityManager(); - - public abstract void start(); - - public abstract void stop(); - - public abstract void commit(); - - public abstract void rollback(); - - public abstract T save(T entity); - - public abstract Object saveWithoutFlush(Object entity); - - public abstract boolean contains(Object entity); - - public abstract void save(Object... entities); - - public abstract Object merge(Object entity); - - public abstract void remove(Object entity); - - public abstract void removeWithoutFlush(Object entity); - - public abstract T reattach(Class entityClass, Object primaryKey); - - public abstract Query createQuery(String hql); - - public abstract T getSingleResult(Query query, T defaultValue); - - public abstract T getEntity(Class entityClass, Object id); - - public abstract T getSingleResult(Class entityClass, Object... criterias); - - public abstract List getResults(Class entityClass, Object... criterias); - - public abstract List getResults(Class entityClass); -} +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.database; + +import org.sonar.api.BatchComponent; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.util.List; + +/** + * This component should not accessible from plugin API + * + * @since 1.10 + */ +public abstract class DatabaseSession implements BatchComponent { + + + // IMPORTANT : this value must be the same than the property + // hibernate.jdbc.batch_size from /META-INF/persistence.xml (module sonar-database) + public static final int BATCH_SIZE = 30; + + + public abstract EntityManager getEntityManager(); + + public abstract void start(); + + public abstract void stop(); + + public abstract void commit(); + + public abstract void rollback(); + + public abstract T save(T entity); + + public abstract Object saveWithoutFlush(Object entity); + + public abstract boolean contains(Object entity); + + public abstract void save(Object... entities); + + public abstract Object merge(Object entity); + + public abstract void remove(Object entity); + + public abstract void removeWithoutFlush(Object entity); + + public abstract T reattach(Class entityClass, Object primaryKey); + + public abstract Query createQuery(String hql); + + public abstract Query createNativeQuery(String sql); + + public abstract T getSingleResult(Query query, T defaultValue); + + public abstract T getEntity(Class entityClass, Object id); + + public abstract T getSingleResult(Class entityClass, Object... criterias); + + public abstract List getResults(Class entityClass, Object... criterias); + + public abstract List getResults(Class entityClass); +} -- 2.39.5