Skip to content

Commit 3752e3d

Browse files
committed
rename classes
1 parent c55b680 commit 3752e3d

File tree

6 files changed

+202
-66
lines changed

6 files changed

+202
-66
lines changed

src/main/java/com/kodgemisi/cigdem/databasebasedmessagesource/BundleEntity.java

-43
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package com.kodgemisi.cigdem.databasebasedmessagesource;
2+
3+
import java.io.Serializable;
4+
import java.util.Comparator;
5+
6+
/**
7+
* <p>You should provide an implementation of this interface as an {@link javax.persistence.Entity} when using {@link JpaBundleContentLoaderStrategy}.</p>
8+
*
9+
* <p>The implementation class should satisfy following conditions:</p>
10+
*
11+
* <ul>
12+
* <li>Should have {@link javax.persistence.Entity} annotation.</li>
13+
* <li>
14+
* Should have unique key constraint for {@code "baseName", "key", "language", "country", "variant"} columns.
15+
* <ul>
16+
* <li>You can achieve that by using: {@code @Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "baseName", "key", "language", "country", "variant" }) })}</li>
17+
* </ul>
18+
* </li>
19+
* <li>You should not enable second level JPA cache for this entity.</li>
20+
* </ul>
21+
*
22+
* <p>
23+
* You can optionally use {@link com.kodgemisi.cigdem.databaseresourcbundle.BundleContentLoaderStrategy#DEFAULT_TABLE_NAME} as table name.
24+
* In which case the database setup would be compatible with {@link com.kodgemisi.cigdem.databaseresourcbundle.DefaultBundleContentLoaderStrategy} as well.
25+
* </p>
26+
*
27+
* Created on January, 2018
28+
*
29+
* @author destans
30+
*/
31+
public interface BundleItem extends Comparable<BundleItem> {
32+
33+
Comparator<String> keyComparator = (s1, s2) -> {
34+
final int s1DotCount = StringUtil.countMatches(s1, '.');
35+
final int s2DotCount = StringUtil.countMatches(s2, '.');
36+
37+
if (s1DotCount == s2DotCount) {
38+
return s1.compareTo(s2);
39+
}
40+
return Integer.compare(s1DotCount, s2DotCount);
41+
};
42+
43+
String getKey();
44+
45+
String getValue();
46+
47+
String getBaseName();
48+
49+
String getLanguage();
50+
51+
String getCountry();
52+
53+
String getVariant();
54+
55+
// This returns a boxed long because this interface is supposed to be implemented by a JPA entity among other things.
56+
Long getLastModified();
57+
58+
default String getName() {
59+
final String baseName = getBaseName();
60+
final String language = StringUtil.getNamePart(getLanguage());
61+
final String country = StringUtil.getNamePart(getCountry());
62+
final String variant = StringUtil.getNamePart(getVariant());
63+
return baseName + language + country + variant;
64+
}
65+
66+
default int compareTo(BundleItem bundleItem) {
67+
68+
if (bundleItem == null) {
69+
return -1;
70+
}
71+
72+
if (this.equals(bundleItem)) {
73+
return 0;
74+
}
75+
76+
//FIXME objects with empty lang, country or variant should have lesser index
77+
return this.getKey().compareTo(bundleItem.getKey());
78+
}
79+
80+
/**
81+
* This class is indented to be used in a JPA entity as a composite id with {@code @IdClass(BundleItem.CompositeId.class)}
82+
*
83+
* <blockquote><pre>
84+
* &#64;IdClass(BundleItem.CompositeId.class)
85+
* &#64;Table(name = BundleContentLoaderStrategy.DEFAULT_TABLE_NAME)
86+
* public class BundleEntity implements BundleItem, Comparable<BundleEntity> {
87+
*
88+
* &#64;Id
89+
* &#64;NotBlank
90+
* private String key;
91+
*
92+
* &#64;NotNull
93+
* &#64;Type(type = "text")
94+
* private String value;
95+
*
96+
* &#64;Id
97+
* &#64;NotBlank
98+
* private String baseName;
99+
*
100+
* &#64;Id
101+
* &#64;NotNull
102+
* private String language = "";
103+
*
104+
* &#64;Id
105+
* &#64;NotNull
106+
* private String country = "";
107+
*
108+
* &#64;Id
109+
* &#64;NotNull
110+
* private String variant = "";
111+
*
112+
* &#64;NotNull
113+
* private Long lastModified;
114+
*
115+
* // getters, setters and other methods...
116+
* }
117+
*
118+
* </pre></blockquote>
119+
*
120+
*/
121+
class CompositeId implements Serializable {
122+
123+
private String key;
124+
125+
private String baseName;
126+
127+
private String language;
128+
129+
private String country;
130+
131+
private String variant;
132+
}
133+
134+
}

src/main/java/com/kodgemisi/cigdem/databasebasedmessagesource/DatabaseResourceBundleAutoconfiguration.java

+3-11
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,22 @@
22

33
import com.kodgemisi.cigdem.databaseresourcbundle.BundleContentLoaderStrategy;
44
import lombok.extern.slf4j.Slf4j;
5-
import org.springframework.beans.factory.config.BeanDefinition;
65
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
76
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
87
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration;
98
import org.springframework.boot.autoconfigure.context.MessageSourceProperties;
109
import org.springframework.boot.context.properties.ConfigurationProperties;
1110
import org.springframework.context.MessageSource;
1211
import org.springframework.context.annotation.Bean;
13-
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
1412
import org.springframework.context.annotation.Configuration;
1513
import org.springframework.context.support.ResourceBundleMessageSource;
16-
import org.springframework.core.type.filter.AnnotationTypeFilter;
17-
import org.springframework.core.type.filter.AssignableTypeFilter;
18-
import org.springframework.util.StopWatch;
1914
import org.springframework.util.StringUtils;
2015

21-
import javax.persistence.Entity;
2216
import javax.persistence.EntityManager;
23-
import javax.persistence.Query;
2417
import javax.persistence.metamodel.EntityType;
2518
import java.time.Duration;
2619
import java.util.ArrayList;
2720
import java.util.List;
28-
import java.util.Set;
2921

3022
/**
3123
* Created on May, 2018
@@ -84,18 +76,18 @@ private MessageSource createMessageSource(MessageSourceProperties properties, Bu
8476
}
8577

8678
@SuppressWarnings("unchecked")
87-
private Class<? extends BundleEntity> findBundleEntityConcreteClass(EntityManager entityManager) throws ClassNotFoundException {
79+
private Class<? extends BundleItem> findBundleEntityConcreteClass(EntityManager entityManager) throws ClassNotFoundException {
8880

8981
final List<Class<?>> entities = new ArrayList<>();
9082

9183
for (EntityType<?> entity : entityManager.getMetamodel().getEntities()) {
92-
if (BundleEntity.class.isAssignableFrom(entity.getJavaType())) {
84+
if (BundleItem.class.isAssignableFrom(entity.getJavaType())) {
9385
entities.add(entity.getJavaType());
9486
}
9587
}
9688

9789
if (entities.size() == 1) {
98-
return (Class<? extends BundleEntity>) entities.get(0);
90+
return (Class<? extends BundleItem>) entities.get(0);
9991
}
10092

10193
if (entities.size() > 1) {

src/main/java/com/kodgemisi/cigdem/databasebasedmessagesource/DatabaseResourceBundleMessageSource.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ protected ResourceBundle doGetBundle(String basename, Locale locale) throws Miss
2828
// As we won't be using it in any way, to reduce complexity we use this class' classloader.
2929
final ClassLoader classLoader = this.getClass().getClassLoader();
3030

31-
// As we can observe here: org.springframework.context.support.ResourceBundleMessageSource.MessageSourceControl#getTimeToLive
31+
// As we can observe here: org.springframework.context.support.ResourceBundleMessageSource.MessageSourceControl.getTimeToLive
3232
// MessageSourceControl's getTimeToLive method should return the same value with the ResourceBundleMessageSource#cacheMillis
33-
// Since we don't put DatabaseResourceBundleControl class as an inner class and since we cannot use
33+
// Since we don't put DatabaseResourceBundleControl class as an inner class as Spring does and since we cannot use
3434
// ResourceBundleMessageSource#getCacheMillis in our DatabaseResourceBundleControl class we have to sync the value by
3535
// explicitly setting TTL in the constructor of DatabaseResourceBundleControl
3636

3737
// Also note that when `spring.messages.cache-duration` is not set, bundles are cached forever.
3838
// Default value of org.springframework.context.support.AbstractResourceBasedMessageSource.cacheMillis is -1 which is equal to
3939
// ResourceBundle.Control.TTL_DONT_CACHE so that's consistent and we don't need to do anything about default value.
40-
// See org.springframework.context.support.AbstractResourceBasedMessageSource.setCacheMillis
40+
// See org.springframework.context.support.AbstractResourceBasedMessageSource#setCacheMillis
4141
return ResourceBundle.getBundle(basename, locale, classLoader, new DatabaseResourceBundleControl(bundleContentLoaderStrategy, getCacheMillis()));
4242
//FIXME DatabaseResourceBundleControl could be a field. However what about using setCacheMillis in runtime?
4343
}

src/main/java/com/kodgemisi/cigdem/databasebasedmessagesource/JpaBundleContentLoaderStrategy.java

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.kodgemisi.cigdem.databasebasedmessagesource;
22

33
import com.kodgemisi.cigdem.databaseresourcbundle.BundleContentLoaderStrategy;
4-
import lombok.AllArgsConstructor;
54
import lombok.RequiredArgsConstructor;
65
import lombok.extern.slf4j.Slf4j;
76

@@ -16,17 +15,17 @@
1615
import java.util.stream.Collectors;
1716

1817
/**
19-
* You should provide an entity which implements {@link BundleEntity} when using this class as
18+
* You should provide an entity which implements {@link BundleItem} when using this class as
2019
* {@link com.kodgemisi.cigdem.databaseresourcbundle.BundleContentLoaderStrategy} for {@link com.kodgemisi.cigdem.databaseresourcbundle.DatabaseResourceBundle}.
2120
*
2221
* Created on June, 2018
2322
*
24-
* @see BundleEntity
23+
* @see BundleItem
2524
* @author destan
2625
*/
2726
@Slf4j
2827
@RequiredArgsConstructor
29-
public class JpaBundleContentLoaderStrategy<E extends BundleEntity> implements BundleContentLoaderStrategy {
28+
public class JpaBundleContentLoaderStrategy<E extends BundleItem> implements BundleContentLoaderStrategy {
3029

3130
private final EntityManager entityManager;
3231

@@ -45,7 +44,7 @@ public Map<String, Object> loadFromDatabase(String bundleName) {
4544

4645
Predicate predicate = builder.equal(root.get("language"), bundleMetaData.language);
4746
predicate = builder.and(predicate, builder.equal(root.get("country"), bundleMetaData.country));
48-
predicate = builder.and(predicate, builder.equal(root.get("name"), bundleMetaData.basename));
47+
predicate = builder.and(predicate, builder.equal(root.get("baseName"), bundleMetaData.basename));
4948
predicate = builder.and(predicate, builder.equal(root.get("variant"), bundleMetaData.variant));
5049

5150
query.distinct(true);
@@ -66,17 +65,17 @@ public Map<String, Object> loadFromDatabase(String bundleName) {
6665

6766
@Override
6867
public boolean needsReload(String baseName, Locale locale, String format, ResourceBundle bundle, long loadTime) {
68+
// Note that this method is only called for expired bundles
6969
try {
7070
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
7171
final CriteriaQuery<Long> query = builder.createQuery(Long.class);
7272
final Root<E> root = query.from(clazz);
7373

74-
query.select(root.get("lastModified"));
74+
query.select(builder.count(root.get("lastModified")));
7575
query.where(builder.greaterThan(root.get("lastModified"), loadTime));
7676

77-
final List<Long> results = entityManager.createQuery(query).getResultList();
78-
79-
return !results.isEmpty();
77+
final long count = entityManager.createQuery(query).getSingleResult();
78+
return count > 0;
8079
}
8180
catch (Exception e) {
8281
// don't let host application crash just because of a database error while getting messages, that would be very unpleasant!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.kodgemisi.cigdem.databasebasedmessagesource;
2+
3+
class StringUtil {
4+
5+
private StringUtil() {
6+
throw new UnsupportedOperationException("Cannot be instantiated!");
7+
}
8+
9+
static String getNamePart(String namePart) {
10+
// Intentionally not using isBlank in order to not mask/shadow any data inconsistency in the db.
11+
// Name parts are never supposed to be blank. They are either empty or valid strings.
12+
return isEmpty(namePart) ? "" : '_' + namePart;
13+
}
14+
15+
static boolean isEmpty(CharSequence cs) {
16+
return cs == null || cs.length() == 0;
17+
}
18+
19+
/**
20+
* <p>Copied from {@link org.apache.commons.lang3.StringUtils#countMatches(java.lang.CharSequence, char)}</p>
21+
*
22+
* <p>Counts how many times the char appears in the given string.</p>
23+
*
24+
* <p>A {@code null} or empty ("") String input returns {@code 0}.</p>
25+
*
26+
* <pre>
27+
* StringUtils.countMatches(null, *) = 0
28+
* StringUtils.countMatches("", *) = 0
29+
* StringUtils.countMatches("abba", 0) = 0
30+
* StringUtils.countMatches("abba", 'a') = 2
31+
* StringUtils.countMatches("abba", 'b') = 2
32+
* StringUtils.countMatches("abba", 'x') = 0
33+
* </pre>
34+
*
35+
* @param str the CharSequence to check, may be null
36+
* @param ch the char to count
37+
* @return the number of occurrences, 0 if the CharSequence is {@code null}
38+
*/
39+
static int countMatches(CharSequence str, char ch) {
40+
if (isEmpty(str)) {
41+
return 0;
42+
} else {
43+
int count = 0;
44+
45+
for(int i = 0; i < str.length(); ++i) {
46+
if (ch == str.charAt(i)) {
47+
++count;
48+
}
49+
}
50+
51+
return count;
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)