Skip to content

Commit

Permalink
Perform intelligent merging in background. Closes #20
Browse files Browse the repository at this point in the history
  • Loading branch information
f43nd1r committed Jul 28, 2018
1 parent 5992593 commit 861f4e6
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 120 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
compile 'com.faendir.proguard:retrace:1.3'
compile 'javax.xml.bind:jaxb-api:2.3.0'
compile 'com.github.ziplet:ziplet:2.3.0'
compile 'me.xdrop:fuzzywuzzy:1.1.10'
//testing
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.security:spring-security-test'
Expand Down
26 changes: 6 additions & 20 deletions src/main/java/com/faendir/acra/model/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,19 @@ public int hashCode() {

@Embeddable
public static class Configuration {
private boolean matchByMessage;
private boolean ignoreInstanceIds;
private boolean ignoreAndroidLineNumbers;
private int minScore;

@PersistenceConstructor
Configuration() {
matchByMessage = true;
ignoreInstanceIds = true;
ignoreAndroidLineNumbers = true;
minScore = 95;
}

public Configuration(boolean matchByMessage, boolean ignoreInstanceIds, boolean ignoreAndroidLineNumbers) {
this.matchByMessage = matchByMessage;
this.ignoreInstanceIds = ignoreInstanceIds;
this.ignoreAndroidLineNumbers = ignoreAndroidLineNumbers;
public Configuration(int minScore) {
this.minScore = minScore;
}

public boolean matchByMessage() {
return matchByMessage;
}

public boolean ignoreInstanceIds() {
return ignoreInstanceIds;
}

public boolean ignoreAndroidLineNumbers() {
return ignoreAndroidLineNumbers;
public int getMinScore() {
return minScore;
}
}
}
88 changes: 88 additions & 0 deletions src/main/java/com/faendir/acra/model/StacktraceMatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.faendir.acra.model;

import org.springframework.data.annotation.PersistenceConstructor;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
* @author lukas
* @since 28.07.18
*/
@Entity
@IdClass(StacktraceMatch.ID.class)
public class StacktraceMatch {
@Id
@ManyToOne
private Stacktrace left;
@Id
@ManyToOne
private Stacktrace right;
private int score;

@PersistenceConstructor
StacktraceMatch(){
}

public StacktraceMatch(Stacktrace left, Stacktrace right, int score) {
this.left = left;
this.right = right;
this.score = score;
}

public Stacktrace getLeft() {
return left;
}

public Stacktrace getRight() {
return right;
}

public List<Stacktrace> getBoth() {
return Arrays.asList(left, right);
}

public int getScore() {
return score;
}

static class ID implements Serializable {
private Stacktrace left;
private Stacktrace right;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ID id = (ID) o;
return Objects.equals(left, id.left) && Objects.equals(right, id.right);
}

@Override
public int hashCode() {
return Objects.hash(left, right);
}
}
}
160 changes: 160 additions & 0 deletions src/main/java/com/faendir/acra/service/BugMerger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.service;

import com.faendir.acra.model.App;
import com.faendir.acra.model.Bug;
import com.faendir.acra.model.QBug;
import com.faendir.acra.model.QStacktrace;
import com.faendir.acra.model.Stacktrace;
import com.faendir.acra.model.StacktraceMatch;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPADeleteClause;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAUpdateClause;
import me.xdrop.fuzzywuzzy.FuzzySearch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static com.faendir.acra.model.QApp.app;
import static com.faendir.acra.model.QBug.bug;
import static com.faendir.acra.model.QStacktrace.stacktrace1;
import static com.faendir.acra.model.QStacktraceMatch.stacktraceMatch;

/**
* @author lukas
* @since 28.07.18
*/
@EnableAsync
@Service
public class BugMerger {
@NonNull private final EntityManager entityManager;

@Autowired
public BugMerger(@NonNull EntityManager entityManager) {
this.entityManager = entityManager;
}

@Async
@Transactional
void checkAutoMerge(@NonNull Stacktrace stacktrace) {
Bug b = new JPAQuery<>(entityManager).from(stacktrace1).where(stacktrace1.eq(stacktrace)).join(stacktrace1.bug, bug).join(bug.app).fetchJoin().select(bug).fetchOne();
if (b != null) {
CloseableIterator<Stacktrace> iterator = new JPAQuery<>(entityManager).from(stacktrace1)
.where(stacktrace1.ne(stacktrace)
.and(stacktrace1.bug.app.eq(JPAExpressions.select(app)
.from(stacktrace1)
.where(stacktrace1.eq(stacktrace))
.join(stacktrace1.bug, bug)
.join(bug.app, app)))
.and(stacktrace1.notIn(JPAExpressions.select(stacktraceMatch.right).from(stacktraceMatch).where(stacktraceMatch.left.eq(stacktrace))))
.and(stacktrace1.notIn(JPAExpressions.select(stacktraceMatch.left).from(stacktraceMatch).where(stacktraceMatch.right.eq(stacktrace)))))
.join(stacktrace1.bug)
.fetchJoin()
.select(stacktrace1)
.iterate();
while (iterator.hasNext()) {
Stacktrace s = iterator.next();
StacktraceMatch match = entityManager.merge(new StacktraceMatch(s, stacktrace, FuzzySearch.ratio(s.getStacktrace(), stacktrace.getStacktrace())));
if (match.getScore() >= b.getApp().getConfiguration().getMinScore()) {
b = mergeBugs(Arrays.asList(b, s.getBug()), s.getBug().getTitle());
}
}
iterator.close();
}
}

@Async
@Transactional
public void changeConfiguration(@NonNull App app, @NonNull App.Configuration configuration) {
app.setConfiguration(configuration);
app = entityManager.merge(app);
QStacktrace stacktrace2 = new QStacktrace("stacktrace2");
QBug bug2 = new QBug("bug2");
CloseableIterator<Tuple> traceIterator = new JPAQuery<>(entityManager).from(stacktrace1)
.join(stacktrace1.bug, bug)
.join(stacktrace2).on(stacktrace1.id.lt(stacktrace2.id))
.join(stacktrace2.bug, bug2)
.where(bug.app.eq(app)
.and(bug2.app.eq(app))
.and(JPAExpressions.selectFrom(stacktraceMatch)
.where(stacktraceMatch.left.eq(stacktrace1)
.and(stacktraceMatch.right.eq(stacktrace2))
.or(stacktraceMatch.left.eq(stacktrace2).and(stacktraceMatch.right.eq(stacktrace1))))
.notExists()))
.select(stacktrace1, stacktrace2)
.iterate();
while (traceIterator.hasNext()){
Tuple tuple = traceIterator.next();
Stacktrace left = tuple.get(stacktrace1);
Stacktrace right = tuple.get(stacktrace2);
assert left != null;
assert right != null;
entityManager.persist(new StacktraceMatch(left, right, FuzzySearch.ratio(left.getStacktrace(), right.getStacktrace())));
}
traceIterator.close();
CloseableIterator<StacktraceMatch> matchIterator = new JPAQuery<>(entityManager).from(stacktraceMatch)
.join(stacktraceMatch.left, stacktrace1)
.fetchJoin()
.join(stacktrace1.bug, bug)
.fetchJoin()
.join(stacktraceMatch.right, stacktrace2)
.fetchJoin()
.join(stacktrace2.bug, bug2)
.fetchJoin()
.where(bug.app.eq(app))
.select(stacktraceMatch)
.iterate();
while (matchIterator.hasNext()) {
StacktraceMatch match = matchIterator.next();
if (match.getScore() >= configuration.getMinScore() && !match.getLeft().getBug().equals(match.getRight().getBug())) {
new JPAUpdateClause(entityManager, stacktrace1).set(stacktrace1.bug, match.getLeft().getBug()).where(stacktrace1.bug.eq(match.getRight().getBug())).execute();
}
}
matchIterator.close();
entityManager.flush();
deleteOrphanBugs();
}

@Transactional
public Bug mergeBugs(@NonNull @Size(min = 2) Collection<Bug> bugs, @NonNull String title) {
List<Bug> list = new ArrayList<>(bugs);
Bug bug = list.remove(0);
bug.setTitle(title);
bug = entityManager.merge(bug);
new JPAUpdateClause(entityManager, stacktrace1).set(stacktrace1.bug, bug).where(stacktrace1.bug.in(list)).execute();
list.forEach(entity -> entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity)));
return bug;
}

@Transactional
public void deleteOrphanBugs() {
new JPADeleteClause(entityManager, bug).where(bug.notIn(JPAExpressions.select(stacktrace1.bug).from(stacktrace1).distinct())).execute();
}
}
Loading

0 comments on commit 861f4e6

Please sign in to comment.