Skip to content

Commit 9f3487f

Browse files
tim-aerotfaulkes
andauthored
Added further options to preload classes (#139)
* Added further options to preload classes Added methods to - preload several classes in one call - preload all classes annotated with @AerospikeRecord from a package (specified by either a String or a Class in the package) Also added a unit test for the same as well as updated the README.md file. * Replaced class.getPackage() with JDK 8 implementation * Move componentType() to getComponentType() * Minor formatting changes * Fixed an indenting issue --------- Co-authored-by: Tim Faulkes <[email protected]>
1 parent 2e05d2a commit 9f3487f

File tree

7 files changed

+468
-141
lines changed

7 files changed

+468
-141
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ The Builder constructor simply takes an IAerospikeClient which it uses for acces
243243
244244
`.preLoadClass(Class<?>)`: Used to load a class before it is needed. The process of loading a class for the first time can be moderately expensive -- there is lots of introspection which goes on to determine how to map the classes to and from the database with the help of the annotations or configuration file. The results of this process are cached so it only has to happen once, and as few introspection calls as possible are called during the actual transformation. If a class is not preloaded, this computation will happen the first time an instance of that class is encountered, resulting in slowdown on the first call.
245245
246+
Another reason to preload a class is situations where an abstract superclass might be read without the subclasses being seen by the AeroMapper first. For example, a list of `Animal` might be stored in the database, but `Animal` is an abstract class with concrete subclasses like `Dog`, `Cat`, etc. If the first call of the AeroMapper is to read a list of `Animal` from the database, there is not enough information to resolve the concrete sub-classes without preloading them.
247+
248+
`.preLoadClasses(Class<?> ...)`: Use to preload several classes before they are called. This is a convenience mechanism which calls `.preLoadClass` for each of the classes in the list.
249+
250+
`.preLoadClassesFromPackage(String | Class<?>)`: Preload all the classes in the specified package which are annotated with `@AerospikeRecord`. The package can be specified by passing a string of the package name or by passing a class in that package. The latter method is preferred as this is less brittle as code is refactored. Note that if a class is passed this class is used only for the package name and does not necessarily need to be a class annotated with `@AerospikeRecord`. Creating a 'marker' class in the package with no functionality and passing to this method is a good way of preventing breaking the preloading as classes are moved around.
251+
246252
`withConfigurationFile`: Whilst mapping information from POJOs via annotations is efficient and has the mapping code inline with the POJO code, there are times when this is not available. For example, if an external library with POJOs is being used and it is desired to map those POJOs to the database, there is no easy way of annotating the source code. Another case this applies is if different mapping parameters are needed between different environments. For example, embedded objects might be stored in a map in development for ease of debugging, but stored in a list in production for compaction of stored data. In these cases an external configuration YAML file can be used to specify how to map the data to the database. See [External Configuration File](#external-configuration-file) for more details. There is an overload of this method which takes an additional boolean parameter -- if this is `true` and the configuration file is not valid, errors will be logged to `stderr` and the process continue. It is normally not recommended to set this parameter to true.
247253
248254
If multiple configuration files are used and the same class is defined in multiple configuration files, the definitions in the first configuration file for a class will be used.

src/main/java/com/aerospike/mapper/tools/AeroMapper.java

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package com.aerospike.mapper.tools;
22

3+
import java.io.BufferedReader;
34
import java.io.File;
45
import java.io.IOException;
56
import java.io.InputStream;
7+
import java.io.InputStreamReader;
68
import java.lang.reflect.Array;
79
import java.util.ArrayList;
10+
import java.util.Arrays;
811
import java.util.List;
12+
import java.util.Set;
913
import java.util.concurrent.atomic.AtomicBoolean;
1014
import java.util.function.Function;
15+
import java.util.stream.Collectors;
1116

1217
import javax.validation.constraints.NotNull;
1318

@@ -31,6 +36,7 @@
3136
import com.aerospike.client.query.Filter;
3237
import com.aerospike.client.query.RecordSet;
3338
import com.aerospike.client.query.Statement;
39+
import com.aerospike.mapper.annotations.AerospikeRecord;
3440
import com.aerospike.mapper.tools.ClassCache.PolicyType;
3541
import com.aerospike.mapper.tools.configuration.ClassConfig;
3642
import com.aerospike.mapper.tools.configuration.Configuration;
@@ -57,7 +63,8 @@ public Builder(IAerospikeClient client) {
5763
}
5864

5965
/**
60-
* Add in a custom type converter. The converter must have methods which implement the ToAerospike and FromAerospike annotation.
66+
* Add in a custom type converter. The converter must have methods which
67+
* implement the ToAerospike and FromAerospike annotation.
6168
*
6269
* @param converter The custom converter
6370
* @return this object
@@ -77,6 +84,64 @@ public Builder preLoadClass(Class<?> clazz) {
7784
return this;
7885
}
7986

87+
public Builder preLoadClasses(Class<?>... clazzes) {
88+
if (classesToPreload == null) {
89+
classesToPreload = new ArrayList<>();
90+
}
91+
classesToPreload.addAll(Arrays.asList(clazzes));
92+
return this;
93+
}
94+
95+
public String getPackageName(Class<?> clazz) {
96+
Class<?> c;
97+
if (clazz.isArray()) {
98+
c = clazz.getComponentType();
99+
} else {
100+
c = clazz;
101+
}
102+
String pn;
103+
if (c.isPrimitive()) {
104+
pn = "java.lang";
105+
} else {
106+
String cn = c.getName();
107+
int dot = cn.lastIndexOf('.');
108+
pn = (dot != -1) ? cn.substring(0, dot).intern() : "";
109+
}
110+
return pn;
111+
}
112+
113+
public Builder preLoadClassesFromPackage(Class<?> classInPackage) {
114+
return preLoadClassesFromPackage(getPackageName(classInPackage));
115+
}
116+
117+
public Builder preLoadClassesFromPackage(String thePackage) {
118+
Set<Class<?>> clazzes = findAllClassesUsingClassLoader(thePackage);
119+
for (Class<?> thisClazz : clazzes) {
120+
// Only add classes with the AerospikeRecord annotation.
121+
if (thisClazz.getAnnotation(AerospikeRecord.class) != null) {
122+
this.preLoadClass(thisClazz);
123+
}
124+
}
125+
return this;
126+
}
127+
128+
// See https://www.baeldung.com/java-find-all-classes-in-package
129+
private Set<Class<?>> findAllClassesUsingClassLoader(String packageName) {
130+
InputStream stream = ClassLoader.getSystemClassLoader()
131+
.getResourceAsStream(packageName.replaceAll("[.]", "/"));
132+
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
133+
return reader.lines().filter(line -> line.endsWith(".class")).map(line -> getClass(line, packageName))
134+
.collect(Collectors.toSet());
135+
}
136+
137+
private Class<?> getClass(String className, String packageName) {
138+
try {
139+
return Class.forName(packageName + "." + className.substring(0, className.lastIndexOf('.')));
140+
} catch (ClassNotFoundException ignored) {
141+
}
142+
return null;
143+
}
144+
80145
public Builder withConfigurationFile(File file) throws IOException {
81146
return this.withConfigurationFile(file, false);
82147
}
@@ -103,7 +168,8 @@ public Builder withConfiguration(String configurationYaml) throws JsonProcessing
103168
return this.withConfiguration(configurationYaml, false);
104169
}
105170

106-
public Builder withConfiguration(String configurationYaml, boolean allowsInvalid) throws JsonProcessingException {
171+
public Builder withConfiguration(String configurationYaml, boolean allowsInvalid)
172+
throws JsonProcessingException {
107173
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
108174
Configuration configuration = objectMapper.readValue(configurationYaml, Configuration.class);
109175
this.loadConfiguration(configuration, allowsInvalid);
@@ -211,22 +277,25 @@ public void save(@NotNull Object object, String... binNames) throws AerospikeExc
211277
}
212278

213279
@Override
214-
public void save(@NotNull WritePolicy writePolicy, @NotNull Object object, String... binNames) throws AerospikeException {
280+
public void save(@NotNull WritePolicy writePolicy, @NotNull Object object, String... binNames)
281+
throws AerospikeException {
215282
save(writePolicy, object, null, binNames);
216283
}
217284

218285
@SuppressWarnings("unchecked")
219-
private <T> void save(WritePolicy writePolicy, @NotNull T object, RecordExistsAction recordExistsAction, String[] binNames) {
286+
private <T> void save(WritePolicy writePolicy, @NotNull T object, RecordExistsAction recordExistsAction,
287+
String[] binNames) {
220288
Class<T> clazz = (Class<T>) object.getClass();
221289
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
222290
if (writePolicy == null) {
223291
writePolicy = new WritePolicy(entry.getWritePolicy());
224292
if (recordExistsAction != null) {
225293
writePolicy.recordExistsAction = recordExistsAction;
226294
}
227-
228-
// #132 -- Ensure that if an overriding TTL / sendkey is passed in the policy it is NOT overwritten. Hence
229-
// only if the policy is null do we override these settings.
295+
296+
// #132 -- Ensure that if an overriding TTL / sendkey is passed in the policy it
297+
// is NOT overwritten. Hence
298+
// only if the policy is null do we override these settings.
230299
Integer ttl = entry.getTtl();
231300
Boolean sendKey = entry.getSendKey();
232301

@@ -261,19 +330,22 @@ public <T> T readFromDigest(@NotNull Class<T> clazz, @NotNull byte[] digest) thr
261330
}
262331

263332
@Override
264-
public <T> T readFromDigest(@NotNull Class<T> clazz, @NotNull byte[] digest, boolean resolveDependencies) throws AerospikeException {
333+
public <T> T readFromDigest(@NotNull Class<T> clazz, @NotNull byte[] digest, boolean resolveDependencies)
334+
throws AerospikeException {
265335
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
266336
Key key = new Key(entry.getNamespace(), digest, entry.getSetName(), null);
267337
return this.read(null, clazz, key, entry, resolveDependencies);
268338
}
269339

270340
@Override
271-
public <T> T readFromDigest(Policy readPolicy, @NotNull Class<T> clazz, @NotNull byte[] digest) throws AerospikeException {
341+
public <T> T readFromDigest(Policy readPolicy, @NotNull Class<T> clazz, @NotNull byte[] digest)
342+
throws AerospikeException {
272343
return this.readFromDigest(readPolicy, clazz, digest, true);
273344
}
274345

275346
@Override
276-
public <T> T readFromDigest(Policy readPolicy, @NotNull Class<T> clazz, @NotNull byte[] digest, boolean resolveDependencies) throws AerospikeException {
347+
public <T> T readFromDigest(Policy readPolicy, @NotNull Class<T> clazz, @NotNull byte[] digest,
348+
boolean resolveDependencies) throws AerospikeException {
277349
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
278350
Key key = new Key(entry.getNamespace(), digest, entry.getSetName(), null);
279351
return this.read(readPolicy, clazz, key, entry, resolveDependencies);
@@ -285,7 +357,8 @@ public <T> T read(@NotNull Class<T> clazz, @NotNull Object userKey) throws Aeros
285357
}
286358

287359
@Override
288-
public <T> T read(@NotNull Class<T> clazz, @NotNull Object userKey, boolean resolveDependencies) throws AerospikeException {
360+
public <T> T read(@NotNull Class<T> clazz, @NotNull Object userKey, boolean resolveDependencies)
361+
throws AerospikeException {
289362
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
290363
String set = entry.getSetName();
291364
Key key = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKey)));
@@ -298,7 +371,8 @@ public <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Object us
298371
}
299372

300373
@Override
301-
public <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Object userKey, boolean resolveDependencies) throws AerospikeException {
374+
public <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Object userKey, boolean resolveDependencies)
375+
throws AerospikeException {
302376
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
303377
String set = entry.getSetName();
304378
Key key = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKey)));
@@ -311,7 +385,8 @@ public <T> T[] read(@NotNull Class<T> clazz, @NotNull Object[] userKeys) throws
311385
}
312386

313387
@Override
314-
public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Object[] userKeys) throws AerospikeException {
388+
public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Object[] userKeys)
389+
throws AerospikeException {
315390
return read(batchPolicy, clazz, userKeys, (Operation[]) null);
316391
}
317392

@@ -321,7 +396,8 @@ public <T> T[] read(@NotNull Class<T> clazz, @NotNull Object[] userKeys, Operati
321396
}
322397

323398
@Override
324-
public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Object[] userKeys, Operation... operations) {
399+
public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Object[] userKeys,
400+
Operation... operations) {
325401
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
326402
String set = entry.getSetName();
327403
Key[] keys = new Key[userKeys.length];
@@ -336,8 +412,9 @@ public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull O
336412
return readBatch(batchPolicy, clazz, keys, entry, operations);
337413
}
338414

339-
@SuppressWarnings({"unchecked"})
340-
private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key, @NotNull ClassCacheEntry<T> entry, boolean resolveDependencies) {
415+
@SuppressWarnings({ "unchecked" })
416+
private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key, @NotNull ClassCacheEntry<T> entry,
417+
boolean resolveDependencies) {
341418
if (readPolicy == null || readPolicy.filterExp == null) {
342419
Object objectForKey = LoadedObjectResolver.get(key);
343420
if (objectForKey != null) {
@@ -367,7 +444,7 @@ private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key,
367444

368445
@SuppressWarnings("unchecked")
369446
private <T> T[] readBatch(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Key[] keys,
370-
@NotNull ClassCacheEntry<T> entry, Operation... operations) {
447+
@NotNull ClassCacheEntry<T> entry, Operation... operations) {
371448
if (batchPolicy == null) {
372449
batchPolicy = entry.getBatchPolicy();
373450
}
@@ -405,7 +482,8 @@ public <T> boolean delete(@NotNull Class<T> clazz, @NotNull Object userKey) thro
405482
}
406483

407484
@Override
408-
public <T> boolean delete(WritePolicy writePolicy, @NotNull Class<T> clazz, @NotNull Object userKey) throws AerospikeException {
485+
public <T> boolean delete(WritePolicy writePolicy, @NotNull Class<T> clazz, @NotNull Object userKey)
486+
throws AerospikeException {
409487
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
410488
Object asKey = entry.translateKeyToAerospikeKey(userKey);
411489

@@ -452,7 +530,8 @@ public <T> void find(@NotNull Class<T> clazz, Function<T, Boolean> function) thr
452530

453531
RecordSet recordSet = null;
454532
try {
455-
// TODO: set the policy (If this statement is thought to be useful, which is dubious)
533+
// TODO: set the policy (If this statement is thought to be useful, which is
534+
// dubious)
456535
recordSet = mClient.query(null, statement);
457536
T result;
458537
while (recordSet.next()) {
@@ -487,7 +566,8 @@ public <T> void scan(@NotNull Class<T> clazz, @NotNull Processor<T> processor, i
487566
}
488567

489568
@Override
490-
public <T> void scan(ScanPolicy policy, @NotNull Class<T> clazz, @NotNull Processor<T> processor, int recordsPerSecond) {
569+
public <T> void scan(ScanPolicy policy, @NotNull Class<T> clazz, @NotNull Processor<T> processor,
570+
int recordsPerSecond) {
491571
ClassCacheEntry<T> entry = MapperUtils.getEntryAndValidateNamespace(clazz, this);
492572
if (policy == null) {
493573
policy = entry.getScanPolicy();
@@ -583,7 +663,8 @@ public <T> VirtualList<T> asBackedList(@NotNull Object object, @NotNull String b
583663
}
584664

585665
@Override
586-
public <T> VirtualList<T> asBackedList(@NotNull Class<?> owningClazz, @NotNull Object key, @NotNull String binName, Class<T> elementClazz) {
666+
public <T> VirtualList<T> asBackedList(@NotNull Class<?> owningClazz, @NotNull Object key, @NotNull String binName,
667+
Class<T> elementClazz) {
587668
return new VirtualList<>(this, owningClazz, key, binName, elementClazz);
588669
}
589670

@@ -631,18 +712,18 @@ private Policy getPolicyByClassAndType(Class<?> clazz, PolicyType policyType) {
631712
ClassCacheEntry<?> entry = ClassCache.getInstance().loadClass(clazz, this);
632713

633714
switch (policyType) {
634-
case READ:
635-
return entry == null ? mClient.getReadPolicyDefault() : entry.getReadPolicy();
636-
case WRITE:
637-
return entry == null ? mClient.getWritePolicyDefault() : entry.getWritePolicy();
638-
case BATCH:
639-
return entry == null ? mClient.getBatchPolicyDefault() : entry.getBatchPolicy();
640-
case SCAN:
641-
return entry == null ? mClient.getScanPolicyDefault() : entry.getScanPolicy();
642-
case QUERY:
643-
return entry == null ? mClient.getQueryPolicyDefault() : entry.getQueryPolicy();
644-
default:
645-
throw new UnsupportedOperationException("Provided unsupported policy.");
715+
case READ:
716+
return entry == null ? mClient.getReadPolicyDefault() : entry.getReadPolicy();
717+
case WRITE:
718+
return entry == null ? mClient.getWritePolicyDefault() : entry.getWritePolicy();
719+
case BATCH:
720+
return entry == null ? mClient.getBatchPolicyDefault() : entry.getBatchPolicy();
721+
case SCAN:
722+
return entry == null ? mClient.getScanPolicyDefault() : entry.getScanPolicy();
723+
case QUERY:
724+
return entry == null ? mClient.getQueryPolicyDefault() : entry.getQueryPolicy();
725+
default:
726+
throw new UnsupportedOperationException("Provided unsupported policy.");
646727
}
647728
}
648729
}

0 commit comments

Comments
 (0)