You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by dr...@apache.org on 2018/10/18 12:24:28 UTC
[2/5] incubator-unomi git commit: UNOMI-204 : More optimizations in
past event count and segment calculation
UNOMI-204 : More optimizations in past event count and segment calculation
Project: http://git-wip-us.apache.org/repos/asf/incubator-unomi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-unomi/commit/ae31c533
Tree: http://git-wip-us.apache.org/repos/asf/incubator-unomi/tree/ae31c533
Diff: http://git-wip-us.apache.org/repos/asf/incubator-unomi/diff/ae31c533
Branch: refs/heads/master
Commit: ae31c533c2f63820bd275c32cf0af0f5c2e6817c
Parents: bc54907
Author: tdraier <dr...@apache.org>
Authored: Fri Oct 12 14:59:54 2018 +0200
Committer: tdraier <dr...@apache.org>
Committed: Fri Oct 12 14:59:54 2018 +0200
----------------------------------------------------------------------
.../unomi/api/services/SegmentService.java | 12 +-
.../ElasticSearchPersistenceServiceImpl.java | 12 +-
.../PastEventConditionESQueryBuilder.java | 114 ++++++++++++++-----
.../resources/OSGI-INF/blueprint/blueprint.xml | 1 +
.../services/services/SegmentServiceImpl.java | 93 +++++++++------
5 files changed, 164 insertions(+), 68 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/ae31c533/api/src/main/java/org/apache/unomi/api/services/SegmentService.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/org/apache/unomi/api/services/SegmentService.java b/api/src/main/java/org/apache/unomi/api/services/SegmentService.java
index 4b03e78..1ed152d 100644
--- a/api/src/main/java/org/apache/unomi/api/services/SegmentService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/SegmentService.java
@@ -21,6 +21,7 @@ import org.apache.unomi.api.Item;
import org.apache.unomi.api.Metadata;
import org.apache.unomi.api.PartialList;
import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.conditions.Condition;
import org.apache.unomi.api.query.Query;
import org.apache.unomi.api.segments.DependentMetadata;
import org.apache.unomi.api.segments.Scoring;
@@ -50,7 +51,7 @@ public interface SegmentService {
/**
* Retrieves segment metadatas for segments in the specified scope, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them are
* retrieved, starting with the {@code offset}-th one.
- *
+ * <p>
* TODO: remove?
*
* @param scope the scope for which we want to retrieve segment metadata
@@ -158,7 +159,7 @@ public interface SegmentService {
* Retrieves the set of all scoring metadata.
*
* @param offset the offset
- * @param size the size
+ * @param size the size
* @param sortBy sort by
* @return the set of all scoring metadata
*/
@@ -219,4 +220,11 @@ public interface SegmentService {
*/
DependentMetadata getScoringDependentMetadata(String scoringId);
+ /**
+ * Get generated property key for past event condition
+ * @param condition The event condition
+ * @param parentCondition The past event condition
+ * @return
+ */
+ String getGeneratedPropertyKey(Condition condition, Condition parentCondition);
}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/ae31c533/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
----------------------------------------------------------------------
diff --git a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index 23cedfb..87a46eb 100644
--- a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -1312,11 +1312,15 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
try {
return conditionESQueryBuilderDispatcher.count(query);
} catch (UnsupportedOperationException e) {
- QueryBuilder filter = conditionESQueryBuilderDispatcher.buildFilter(query);
- if (filter instanceof IdsQueryBuilder) {
- return ((IdsQueryBuilder) filter).ids().size();
+ try {
+ QueryBuilder filter = conditionESQueryBuilderDispatcher.buildFilter(query);
+ if (filter instanceof IdsQueryBuilder) {
+ return ((IdsQueryBuilder) filter).ids().size();
+ }
+ return queryCount(filter, itemType);
+ } catch (UnsupportedOperationException e1) {
+ return -1;
}
- return queryCount(filter, itemType);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/ae31c533/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java
----------------------------------------------------------------------
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java
index b3c169c..c891abd 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java
@@ -21,6 +21,7 @@ import org.apache.unomi.api.Event;
import org.apache.unomi.api.Profile;
import org.apache.unomi.api.conditions.Condition;
import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.SegmentService;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilder;
import org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilderDispatcher;
@@ -28,12 +29,20 @@ import org.apache.unomi.persistence.spi.PersistenceService;
import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.RangeQueryBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.*;
public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder {
+
private DefinitionsService definitionsService;
private PersistenceService persistenceService;
+ private SegmentService segmentService;
+
+ private int maximumIdsQueryCount = 1000;
+ private int termsAggregatePartitionSize = 1000;
public void setDefinitionsService(DefinitionsService definitionsService) {
this.definitionsService = definitionsService;
@@ -43,62 +52,115 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder
this.persistenceService = persistenceService;
}
+ public void setMaximumIdsQueryCount(int maximumIdsQueryCount) {
+ this.maximumIdsQueryCount = maximumIdsQueryCount;
+ }
+
+ public void setTermsAggregatePartitionSize(int termsAggregatePartitionSize) {
+ this.termsAggregatePartitionSize = termsAggregatePartitionSize;
+ }
+
+ public void setSegmentService(SegmentService segmentService) {
+ this.segmentService = segmentService;
+ }
+
public QueryBuilder buildQuery(Condition condition, Map<String, Object> context, ConditionESQueryBuilderDispatcher dispatcher) {
- Condition eventCondition = getEventCondition(condition, context);
- //todo : Check behaviour with important number of profiles
- Set<String> ids = new HashSet<String>();
- Integer minimumEventCount = condition.getParameter("minimumEventCount") == null ? 0 : (Integer) condition.getParameter("minimumEventCount");
+ Integer minimumEventCount = condition.getParameter("minimumEventCount") == null ? 1 : (Integer) condition.getParameter("minimumEventCount");
Integer maximumEventCount = condition.getParameter("maximumEventCount") == null ? Integer.MAX_VALUE : (Integer) condition.getParameter("maximumEventCount");
- Map<String, Double> m = persistenceService.getSingleValuesMetrics(eventCondition, new String[]{"card", "count"}, "profileId.keyword", Event.ITEM_TYPE);
- long card = m.get("_card").longValue();
+ final Logger logger = LoggerFactory.getLogger(PastEventConditionESQueryBuilder.class.getName());
+
+ if (condition.getParameter("generatedPropertyKey") != null && condition.getParameter("generatedPropertyKey").equals(segmentService.getGeneratedPropertyKey((Condition) condition.getParameter("eventCondition"), condition))) {
+ // A property is already set on profiles matching the past event condition, use it
+ if (minimumEventCount != 1 || maximumEventCount != Integer.MAX_VALUE) {
+ // Check the number of occurences
+ RangeQueryBuilder builder = QueryBuilders.rangeQuery("systemProperties.pastEvents." + condition.getParameter("generatedPropertyKey"));
+ if (minimumEventCount != 1) {
+ builder.gte(minimumEventCount);
+ }
+ if (maximumEventCount != Integer.MAX_VALUE) {
+ builder.lte(minimumEventCount);
+ }
+ return builder;
+ } else {
+ // Simply get profiles who have the property set
+ return QueryBuilders.existsQuery("systemProperties.pastEvents." + condition.getParameter("generatedPropertyKey"));
+ }
+ } else {
+ // No property set - tries to build an idsQuery
+ // Build past event condition
+ Condition eventCondition = getEventCondition(condition, context);
+
+ Set<String> ids = new HashSet<>();
- int numParts = (int) (card / 1000);
- for (int i = 0; i < numParts; i++) {
- Map<String, Long> eventCountByProfile = persistenceService.aggregateWithOptimizedQuery(eventCondition, new TermsAggregate("profileId", i, numParts), Event.ITEM_TYPE);
- if (eventCountByProfile != null) {
- for (Map.Entry<String, Long> entry : eventCountByProfile.entrySet()) {
- if (!entry.getKey().startsWith("_")) {
- if (entry.getValue() >= minimumEventCount && entry.getValue() <= maximumEventCount) {
+ // Get full cardinality to partition the terms aggreggation
+ Map<String, Double> m = persistenceService.getSingleValuesMetrics(eventCondition, new String[]{"card"}, "profileId.keyword", Event.ITEM_TYPE);
+ long card = m.get("_card").longValue();
+
+ int numParts = (int) (card / termsAggregatePartitionSize);
+ for (int i = 0; i < numParts; i++) {
+ Map<String, Long> eventCountByProfile = persistenceService.aggregateWithOptimizedQuery(eventCondition, new TermsAggregate("profileId", i, numParts), Event.ITEM_TYPE);
+ if (eventCountByProfile != null) {
+ eventCountByProfile.remove("_filtered");
+ for (Map.Entry<String, Long> entry : eventCountByProfile.entrySet()) {
+ if (entry.getValue() < minimumEventCount) {
+ // No more interesting buckets in this partition
+ break;
+ } else if (entry.getValue() <= maximumEventCount) {
ids.add(entry.getKey());
+
+ if (ids.size() > maximumIdsQueryCount) {
+ // Avoid building too big ids query - throw exception instead
+ throw new UnsupportedOperationException("Too many profiles");
+ }
}
}
}
}
- }
- return QueryBuilders.idsQuery(Profile.ITEM_TYPE).addIds(ids.toArray(new String[ids.size()]));
+ return QueryBuilders.idsQuery(Profile.ITEM_TYPE).addIds(ids.toArray(new String[0]));
+ }
}
public long count(Condition condition, Map<String, Object> context, ConditionESQueryBuilderDispatcher dispatcher) {
Condition eventCondition = getEventCondition(condition, context);
- Integer minimumEventCount = condition.getParameter("minimumEventCount") == null ? 0 : (Integer) condition.getParameter("minimumEventCount");
+ Integer minimumEventCount = condition.getParameter("minimumEventCount") == null ? 1 : (Integer) condition.getParameter("minimumEventCount");
Integer maximumEventCount = condition.getParameter("maximumEventCount") == null ? Integer.MAX_VALUE : (Integer) condition.getParameter("maximumEventCount");
- Map<String, Double> m = persistenceService.getSingleValuesMetrics(eventCondition, new String[]{"card", "count"}, "profileId.keyword", Event.ITEM_TYPE);
+ // Get full cardinality to partition the terms aggreggation
+ Map<String, Double> m = persistenceService.getSingleValuesMetrics(eventCondition, new String[]{"card"}, "profileId.keyword", Event.ITEM_TYPE);
long card = m.get("_card").longValue();
- long count = m.get("_count").longValue();
- if (minimumEventCount != 0 || maximumEventCount != Integer.MAX_VALUE) {
+ if (minimumEventCount != 1 || maximumEventCount != Integer.MAX_VALUE) {
+ // Event count specified, must check occurences count for each profile
int result = 0;
- int numParts = (int) (card / 1000);
+ int numParts = (int) (card / termsAggregatePartitionSize);
for (int i = 0; i < numParts; i++) {
Map<String, Long> eventCountByProfile = persistenceService.aggregateWithOptimizedQuery(eventCondition, new TermsAggregate("profileId", i, numParts), Event.ITEM_TYPE);
+ int j = 0;
if (eventCountByProfile != null) {
+ eventCountByProfile.remove("_filtered");
for (Map.Entry<String, Long> entry : eventCountByProfile.entrySet()) {
- if (!entry.getKey().startsWith("_")) {
- if (entry.getValue() >= minimumEventCount && entry.getValue() <= maximumEventCount) {
- result ++;
- }
+ if (entry.getValue() < minimumEventCount) {
+ // No more interesting buckets in this partition
+ break;
+ } else if (entry.getValue() <= maximumEventCount && minimumEventCount == 1) {
+ // Take all remaining elements
+ result += eventCountByProfile.size() - j;
+ break;
+ } else if (entry.getValue() <= maximumEventCount) {
+ result++;
}
+ j++;
}
}
}
return result;
+ } else {
+ // Simply get the full number of distinct profiles
+ return card;
}
-
- return card;
}
private Condition getEventCondition(Condition condition, Map<String, Object> context) {
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/ae31c533/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 2cf78ab..9aa9d6c 100644
--- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -93,6 +93,7 @@
<bean class="org.apache.unomi.plugins.baseplugin.conditions.PastEventConditionESQueryBuilder">
<property name="definitionsService" ref="definitionsService"/>
<property name="persistenceService" ref="persistenceService"/>
+ <property name="segmentService" ref="segmentService"/>
</bean>
</service>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/ae31c533/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java
----------------------------------------------------------------------
diff --git a/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java
index 0fe5f2e..c68f691 100644
--- a/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java
@@ -27,7 +27,6 @@ import org.apache.unomi.api.rules.Rule;
import org.apache.unomi.api.segments.*;
import org.apache.unomi.api.services.*;
import org.apache.unomi.persistence.spi.CustomObjectMapper;
-import org.apache.unomi.persistence.spi.PersistenceService;
import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -58,6 +57,7 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
private List<Scoring> allScoring;
private Timer segmentTimer;
private int segmentUpdateBatchSize = 1000;
+ private int termsAggregatePartitionSize = 1000;
public SegmentServiceImpl() {
logger.info("Initializing segment service...");
@@ -83,6 +83,10 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
this.segmentUpdateBatchSize = segmentUpdateBatchSize;
}
+ public void setTermsAggregatePartitionSize(int termsAggregatePartitionSize) {
+ this.termsAggregatePartitionSize = termsAggregatePartitionSize;
+ }
+
public void postConstruct() {
logger.debug("postConstruct {" + bundleContext.getBundle() + "}");
loadPredefinedSegments(bundleContext);
@@ -708,13 +712,8 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
private void getAutoGeneratedRules(Metadata metadata, Condition condition, Condition parentCondition, List<Rule> rules) {
Set<String> tags = condition.getConditionType().getMetadata().getSystemTags();
if (tags.contains("eventCondition") && !tags.contains("profileCondition")) {
- try {
- Map<String, Object> m = new HashMap<>(3);
- m.put("scope", metadata.getScope());
- m.put("condition", condition);
- m.put("numberOfDays", parentCondition.getParameter("numberOfDays"));
- String key = CustomObjectMapper.getObjectMapper().writeValueAsString(m);
- key = "eventTriggered" + getMD5(key);
+ String key = getGeneratedPropertyKey(condition, parentCondition);
+ if (key != null) {
parentCondition.setParameter("generatedPropertyKey", key);
Rule rule = rulesService.getRule(key);
if (rule == null) {
@@ -734,8 +733,6 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
rule.getLinkedItems().add(metadata.getId());
rules.add(rule);
}
- } catch (JsonProcessingException e) {
- logger.error(e.getMessage(), e);
}
} else {
Collection<Object> values = new ArrayList<>(condition.getParameterValues().values());
@@ -773,18 +770,25 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
l.add(numberOfDaysCondition);
}
String propertyKey = (String) parentCondition.getParameter("generatedPropertyKey");
- Map<String, Long> eventCountByProfile = persistenceService.aggregateWithOptimizedQuery(andCondition, new TermsAggregate("profileId"), Event.ITEM_TYPE);
- for (Map.Entry<String, Long> entry : eventCountByProfile.entrySet()) {
- String profileId = entry.getKey();
- if (!profileId.startsWith("_")) {
- Map<String, Long> pastEventCounts = new HashMap<>();
- pastEventCounts.put(propertyKey, entry.getValue());
- Map<String, Object> systemProperties = new HashMap<>();
- systemProperties.put("pastEvents", pastEventCounts);
- try {
- persistenceService.update(profileId, null, Profile.class, "systemProperties", systemProperties);
- } catch (Exception e) {
- logger.error("Error updating profile {} past event system properties", profileId, e);
+
+ Map<String, Double> m = persistenceService.getSingleValuesMetrics(andCondition, new String[]{"card"}, "profileId.keyword", Event.ITEM_TYPE);
+ long card = m.get("_card").longValue();
+
+ int numParts = (int) (card / termsAggregatePartitionSize);
+ for (int i = 0; i < numParts; i++) {
+ Map<String, Long> eventCountByProfile = persistenceService.aggregateWithOptimizedQuery(andCondition, new TermsAggregate("profileId", i, numParts), Event.ITEM_TYPE);
+ for (Map.Entry<String, Long> entry : eventCountByProfile.entrySet()) {
+ String profileId = entry.getKey();
+ if (!profileId.startsWith("_")) {
+ Map<String, Long> pastEventCounts = new HashMap<>();
+ pastEventCounts.put(propertyKey, entry.getValue());
+ Map<String, Object> systemProperties = new HashMap<>();
+ systemProperties.put("pastEvents", pastEventCounts);
+ try {
+ persistenceService.update(profileId, null, Profile.class, "systemProperties", systemProperties);
+ } catch (Exception e) {
+ logger.error("Error updating profile {} past event system properties", profileId, e);
+ }
}
}
}
@@ -792,6 +796,33 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
logger.info("Profiles past condition updated in {}ms", System.currentTimeMillis() - t);
}
+ public String getGeneratedPropertyKey(Condition condition, Condition parentCondition) {
+ try {
+ Map<String, Object> m = new HashMap<>();
+ m.put("condition", condition);
+ m.put("numberOfDays", parentCondition.getParameter("numberOfDays"));
+ String key = CustomObjectMapper.getObjectMapper().writeValueAsString(m);
+ return "eventTriggered" + getMD5(key);
+ } catch (JsonProcessingException e) {
+ logger.error("Cannot generate key",e);
+ return null;
+ }
+ }
+
+ private String getMD5(String md5) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] array = md.digest(md5.getBytes());
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < array.length; ++i) {
+ sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
+ }
+ return sb.toString();
+ } catch (java.security.NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void updateExistingProfilesForSegment(Segment segment) {
long t = System.currentTimeMillis();
Condition segmentCondition = new Condition();
@@ -830,6 +861,7 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
PartialList<Profile> profilesToAdd = persistenceService.query(profilesToAddCondition, null, Profile.class, 0, segmentUpdateBatchSize, "10m");
while (profilesToAdd.getList().size() > 0) {
+ long t2= System.currentTimeMillis();
for (Profile profileToAdd : profilesToAdd.getList()) {
profileToAdd.getSegments().add(segment.getItemId());
persistenceService.update(profileToAdd.getItemId(), null, Profile.class, "segments", profileToAdd.getSegments());
@@ -838,12 +870,14 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
eventService.send(profileUpdated);
updatedProfileCount++;
}
+ logger.info("{} profiles added in segment in {}ms", profilesToAdd.size(), System.currentTimeMillis() - t2);
profilesToAdd = persistenceService.continueScrollQuery(Profile.class, profilesToAdd.getScrollIdentifier(), profilesToAdd.getScrollTimeValidity());
if (profilesToAdd == null || profilesToAdd.getList().size() == 0) {
break;
}
}
while (profilesToRemove.getList().size() > 0) {
+ long t2= System.currentTimeMillis();
for (Profile profileToRemove : profilesToRemove.getList()) {
profileToRemove.getSegments().remove(segment.getItemId());
persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "segments", profileToRemove.getSegments());
@@ -852,6 +886,7 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
eventService.send(profileUpdated);
updatedProfileCount++;
}
+ logger.info("{} profiles removed from segment in {}ms", profilesToRemove.size(), System.currentTimeMillis() - t2);
profilesToRemove = persistenceService.continueScrollQuery(Profile.class, profilesToRemove.getScrollIdentifier(), profilesToRemove.getScrollTimeValidity());
if (profilesToRemove == null || profilesToRemove.getList().size() == 0) {
break;
@@ -929,20 +964,6 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe
logger.info("Profiles updated in {}ms", System.currentTimeMillis() - t);
}
- private String getMD5(String md5) {
- try {
- MessageDigest md = MessageDigest.getInstance("MD5");
- byte[] array = md.digest(md5.getBytes());
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < array.length; ++i) {
- sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
- }
- return sb.toString();
- } catch (java.security.NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
public void bundleChanged(BundleEvent event) {
switch (event.getType()) {
case BundleEvent.STARTED: