mirror of
https://github.com/SonarSource/sonarqube.git
synced 2024-08-03 08:18:00 +02:00
[SONAR-2347] Close a review when its corresponding violation does not
exist anymore
This commit is contained in:
parent
da7c05cdf4
commit
8d294104c3
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String> 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<RuleFailureModel> 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<RuleFailureModel> pastViolations) {
|
||||
Multimap<Rule, RuleFailureModel> 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<String> getChecksums(SnapshotSource source) {
|
||||
return source == null || source.getData() == null ? Collections.<String>emptyList() : getChecksums(source.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data can't be null
|
||||
*/
|
||||
static List<String> getChecksums(String data) {
|
||||
String[] lines = data.split("\r?\n|\r", -1);
|
||||
List<String> 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<String> 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<Rule, RuleFailureModel> pastViolationsByRule) {
|
||||
Collection<RuleFailureModel> 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<RuleFailureModel> 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<RuleFailureModel> 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<String> 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<RuleFailureModel> 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<RuleFailureModel> pastViolations) {
|
||||
Multimap<Rule, RuleFailureModel> 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<String> getChecksums(SnapshotSource source) {
|
||||
return source == null || source.getData() == null ? Collections.<String>emptyList() : getChecksums(source.getData());
|
||||
}
|
||||
|
||||
static List<String> getChecksums(String data) {
|
||||
List<String> result = Lists.newArrayList();
|
||||
StringInputStream stream = new StringInputStream(data);
|
||||
try {
|
||||
List<String> 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<String> 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<Rule, RuleFailureModel> pastViolationsByRule) {
|
||||
Collection<RuleFailureModel> 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<RuleFailureModel> 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<RuleFailureModel> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<dataset>
|
||||
|
||||
<snapshots
|
||||
id="11"
|
||||
project_id="555"/>
|
||||
<snapshots
|
||||
id="111"
|
||||
project_id="555"/>
|
||||
<snapshots
|
||||
id="22"
|
||||
project_id="666"/>
|
||||
<snapshots
|
||||
id="222"
|
||||
project_id="666"/>
|
||||
|
||||
<rule_failures
|
||||
id="1"
|
||||
permament_id="1"
|
||||
snapshot_id="11"/>
|
||||
<rule_failures
|
||||
id="2"
|
||||
permament_id="2"
|
||||
snapshot_id="22"/>
|
||||
<rule_failures
|
||||
id="3"
|
||||
permament_id="3"
|
||||
snapshot_id="22"/>
|
||||
<rule_failures
|
||||
id="4"
|
||||
permament_id="1"
|
||||
snapshot_id="111"/>
|
||||
<rule_failures
|
||||
id="5"
|
||||
permament_id="3"
|
||||
snapshot_id="222"/>
|
||||
|
||||
<reviews
|
||||
id="1"
|
||||
status="open"
|
||||
rule_failure_permanent_id="1"
|
||||
resource_id="555"/>
|
||||
<reviews
|
||||
id="2"
|
||||
status="open"
|
||||
rule_failure_permanent_id="2"
|
||||
resource_id="666"/>
|
||||
<reviews
|
||||
id="3"
|
||||
status="open"
|
||||
rule_failure_permanent_id="3"
|
||||
resource_id="666"/>
|
||||
|
||||
</dataset>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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> 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> T reattach(Class<T> 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 <code>defaultValue</code>, if not found
|
||||
* @throws NonUniqueResultException if more than one result
|
||||
*/
|
||||
public <T> 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<T> result = query.getResultList();
|
||||
|
||||
if (result.size() == 1) {
|
||||
return result.get(0);
|
||||
|
||||
} else if (result.isEmpty()) {
|
||||
return defaultValue;
|
||||
|
||||
} else {
|
||||
Set<T> uniqueResult = new HashSet<T>(result);
|
||||
if (uniqueResult.size() > 1) {
|
||||
throw new NonUniqueResultException("Expected single result, but got : " + result.toString());
|
||||
} else {
|
||||
return uniqueResult.iterator().next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getEntity(Class<T> entityClass, Object id) {
|
||||
startTransaction();
|
||||
return getEntityManager().find(entityClass, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the result or <code>null</code>, if not found
|
||||
* @throws NonUniqueResultException if more than one result
|
||||
*/
|
||||
public <T> T getSingleResult(Class<T> 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 <T> List<T> getResults(Class<T> entityClass, Object... criterias) {
|
||||
return getQueryForCriterias(entityClass, true, criterias).getResultList();
|
||||
}
|
||||
|
||||
public <T> List<T> getResults(Class<T> 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<String, Object> mappedCriterias = new HashMap<String, Object>();
|
||||
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<String, Object> entry : mappedCriterias.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
return getEntityManager().createQuery(hql.toString());
|
||||
}
|
||||
|
||||
private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) {
|
||||
for (Iterator<String> 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> 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> T reattach(Class<T> 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 <code>defaultValue</code>, if not found
|
||||
* @throws NonUniqueResultException if more than one result
|
||||
*/
|
||||
public <T> 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<T> result = query.getResultList();
|
||||
|
||||
if (result.size() == 1) {
|
||||
return result.get(0);
|
||||
|
||||
} else if (result.isEmpty()) {
|
||||
return defaultValue;
|
||||
|
||||
} else {
|
||||
Set<T> uniqueResult = new HashSet<T>(result);
|
||||
if (uniqueResult.size() > 1) {
|
||||
throw new NonUniqueResultException("Expected single result, but got : " + result.toString());
|
||||
} else {
|
||||
return uniqueResult.iterator().next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getEntity(Class<T> entityClass, Object id) {
|
||||
startTransaction();
|
||||
return getEntityManager().find(entityClass, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the result or <code>null</code>, if not found
|
||||
* @throws NonUniqueResultException if more than one result
|
||||
*/
|
||||
public <T> T getSingleResult(Class<T> 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 <T> List<T> getResults(Class<T> entityClass, Object... criterias) {
|
||||
return getQueryForCriterias(entityClass, true, criterias).getResultList();
|
||||
}
|
||||
|
||||
public <T> List<T> getResults(Class<T> 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<String, Object> mappedCriterias = new HashMap<String, Object>();
|
||||
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<String, Object> entry : mappedCriterias.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
return getEntityManager().createQuery(hql.toString());
|
||||
}
|
||||
|
||||
private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) {
|
||||
for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) {
|
||||
String criteria = i.next();
|
||||
hql.append("o.").append(criteria).append("=:").append(criteria);
|
||||
if (i.hasNext()) {
|
||||
hql.append(" AND ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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> 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> T reattach(Class<T> entityClass, Object primaryKey);
|
||||
|
||||
public abstract Query createQuery(String hql);
|
||||
|
||||
public abstract <T> T getSingleResult(Query query, T defaultValue);
|
||||
|
||||
public abstract <T> T getEntity(Class<T> entityClass, Object id);
|
||||
|
||||
public abstract <T> T getSingleResult(Class<T> entityClass, Object... criterias);
|
||||
|
||||
public abstract <T> List<T> getResults(Class<T> entityClass, Object... criterias);
|
||||
|
||||
public abstract <T> List<T> getResults(Class<T> 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> 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> T reattach(Class<T> entityClass, Object primaryKey);
|
||||
|
||||
public abstract Query createQuery(String hql);
|
||||
|
||||
public abstract Query createNativeQuery(String sql);
|
||||
|
||||
public abstract <T> T getSingleResult(Query query, T defaultValue);
|
||||
|
||||
public abstract <T> T getEntity(Class<T> entityClass, Object id);
|
||||
|
||||
public abstract <T> T getSingleResult(Class<T> entityClass, Object... criterias);
|
||||
|
||||
public abstract <T> List<T> getResults(Class<T> entityClass, Object... criterias);
|
||||
|
||||
public abstract <T> List<T> getResults(Class<T> entityClass);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user