private Prop findRelProperty(Class<?> fromCls, String rel, Class<?> toCls) { Object entity = !fromCls.isInterface() ? data.constructor.create(fromCls) : null; for (Prop prop : Beany.propertiesOf(fromCls).select(data.relPropSelector)) { String relName = null; if (!fromCls.isInterface()) { Object value = prop.getRaw(entity); if (hasEntityLinks(value)) { EntityLinks links = entityLinks(value); relName = links.relationName(); } } else { Rel relation = prop.getAnnotation(Rel.class); if (relation != null) { relName = relation.value(); } } if (relName != null && relName.equals(rel)) { if (prop.getRawTypeArg(0).equals(toCls)) { return prop; } } } Log.warn( "Didn't find inverse relation property!", "relation", rel, "from", fromCls, "to", toCls); return null; }
private void resolveDoubleFileInconsistency() { String file1 = currentFile().getName(); String file2 = otherFile().getName(); Log.warn( "The database was left in inconsistent state, both files exist!", "file1", file1, "file2", file2); long modif1, modif2; try { modif1 = (Long) loadMetadata(new FileInputStream(currentFile())).get(META_TIMESTAMP); modif2 = (Long) loadMetadata(new FileInputStream(otherFile())).get(META_TIMESTAMP); } catch (FileNotFoundException e) { throw new RuntimeException(e); } U.must( modif1 != modif2, "Cannot determine which database file to remove, please remove the incorrect file manually!"); // delete the most recent file, since it wasn't written completely File recent = modif1 > modif2 ? currentFile() : otherFile(); Log.warn( "The more recent database file is assumed incomplete, so it will be deleted!", "file", recent); recent.delete(); }
@Test public void testTCPClientWithDefaultConnections() { Log.setLogLevel(LogLevel.DEBUG); TCPClient client = TCP.client() .host("localhost") .port(8080) .connections(5) .protocol(HI_CLIENT) .build() .start(); // let the clients wait UTILS.sleep(3000); TCPServer server = TCP.server().port(8080).protocol(UPPERCASE_SERVER).build().start(); // let the server serve the clients UTILS.sleep(3000); eq(client.info().messagesProcessed(), 5); eq(server.info().messagesProcessed(), 5); client.shutdown(); server.shutdown(); }
@Test public void testTCPClientWithDefaultAndCustomConnections() { Log.setLogLevel(LogLevel.DEBUG); TCPClient client = TCP.client() .host("127.0.0.1") .port(8080) .connections(3) .protocol(HI_CLIENT) .build() .start(); client.connect("localhost", 9090, HI_CLIENT, 2, false, null); // let the clients wait UTILS.sleep(3000); TCPServer server1 = TCP.server().port(8080).protocol(UPPERCASE_SERVER).build().start(); TCPServer server2 = TCP.server().port(9090).protocol(UPPERCASE_SERVER).build().start(); // let the servers serve the clients UTILS.sleep(3000); eq(client.info().messagesProcessed(), 5); eq(server1.info().messagesProcessed(), 3); eq(server2.info().messagesProcessed(), 2); client.shutdown(); server1.shutdown(); server2.shutdown(); }
@Test public void testAsyncHttpServer() { Log.debugging(); On.req( req -> { U.must(!req.isAsync()); req.async(); U.must(req.isAsync()); Jobs.after(10, TimeUnit.MILLISECONDS) .run( () -> { IO.write(req.out(), "O"); Jobs.after(10, TimeUnit.MILLISECONDS) .run( () -> { IO.write(req.out(), "K"); req.done(); }); }); return req; }); Self.get("/").expect("OK").benchmark(1, 100, 10000); Self.post("/").expect("OK").benchmark(1, 100, 10000); }
public void delete(long id) { sharedLock(); try { failIfReadonlyTx(); validateId(id); Rec old = data.data.get(id); Object entity = obj(old); secureDelete(entity); boolean removed = data.data.remove(id, old); occErrorIf( !removed, "Concurrent modification occured while deleting the object with ID=%s!", id); if (data.insideTx.get()) { data.txChanges.putIfAbsent(id, old); } deleteRelsFor(entity); data.lastChangedOn.set(System.currentTimeMillis()); Log.audit("Deleted DB record", "id", id); } finally { sharedUnlock(); } }
private RelPair getRelPair(Object entity, Prop prop, String rel, boolean inverse) { Class<?> cls = prop.getRawTypeArg(0); Class<? extends Object> entCls = Cls.unproxy(entity.getClass()); Class<?> srcType = inverse ? cls : entCls; Class<?> destType = inverse ? entCls : cls; Tuple key = new Tuple(rel, srcType, destType); RelPair relPair = data.relPairs.get(key); Prop srcProp, destProp; if (relPair != null) { srcProp = relPair.srcProp; destProp = relPair.destProp; } else { String invRel = inverse ? rel : "^" + rel; Prop p = findRelProperty(cls, invRel, entCls); srcProp = inverse ? p : prop; destProp = inverse ? prop : p; relPair = new RelPair(rel, srcType, destType, srcProp, destProp); data.relPairs.putIfAbsent(key, relPair); if (srcType == null || srcProp == null || destType == null || destProp == null) { Log.warn("Incomplete relation pair!", "relation", relPair); } } return relPair; }
public static void main(String[] args) { Map<String, String> cfg = U.map("title", "App", "user", "u1", "theme", "x"); On.get("/cfg").json(cfg); PageMenu menu = PageMenu.from(U.map("Home", "/", "Groups", "/group")); On.defaultWrap((data, next) -> next.invoke(x -> page(x).menu(menu).title(cfg.get("title")))); List<Btn> letters = Flow.chars('a', 'z').map(c -> btn(c).linkTo("/find?p=" + c)).toList(); On.page("/") .gui("p", p -> U.list(letters, grid(cfg).keyView(k -> a(k).href("/edit?key=" + k)))); On.page("/group") .gui( "x", x -> { return Do.map(Do.group(cfg).by((k, v) -> k.charAt(0))) .toList((k, v) -> U.list(h3(k), grid(v))); }); On.page("/find") .gui("p", p -> U.list(letters, grid(Find.allOf(cfg).where((k, v) -> k.startsWith(p))))); Btn ok = btn("Update").onClick(() -> Log.info("Updated", "cfg", cfg)); On.page("/edit").gui("key", key -> edit(cfg, key).buttons(ok, btn("Back").linkTo("/"))); Runnable replicate = () -> REST.get("http://localhost:8888/cfg", Object.class, U::print); Jobs.scheduleAtFixedRate(replicate, 3, 3, TimeUnit.SECONDS); }
private long _insert(Object record, boolean failOnReadOnlyTx) { U.notNull(record, "record"); secureInsert(record); sharedLock(); try { if (failOnReadOnlyTx) { failIfReadonlyTx(); } long id = data.ids.incrementAndGet(); Beany.setId(record, id); // Optimistic concurrency control through the "version" property if (Beany.hasProperty(record, VERSION)) { // FIXME rollback version in TX fails Beany.setPropValue(record, VERSION, 1); } Date now = new Date(); if (Beany.hasProperty(record, CREATED_BY)) { Beany.setPropValue(record, CREATED_BY, username()); } if (Beany.hasProperty(record, CREATED_ON)) { Beany.setPropValue(record, CREATED_ON, now); } if (Beany.hasProperty(record, LAST_UPDATED_BY)) { Beany.setPropValue(record, LAST_UPDATED_BY, username()); } if (Beany.hasProperty(record, LAST_UPDATED_ON)) { Beany.setPropValue(record, LAST_UPDATED_ON, now); } if (data.insideTx.get()) { if (data.txInsertions.putIfAbsent(id, INSERTION) != null) { throw new IllegalStateException("Cannot insert changelog record with existing ID: " + id); } } if (data.data.putIfAbsent(id, rec(record)) != null) { throw new IllegalStateException("Cannot insert record with existing ID: " + id); } updateChangesFromRels(record); data.lastChangedOn.set(System.currentTimeMillis()); Log.audit("Inserted DB record", "id", id); return id; } finally { sharedUnlock(); } }
private void invokeCallbacks(Callback<Void>[] callbacks, Throwable e) { for (Callback<Void> callback : callbacks) { try { callback.onDone(null, e); } catch (Throwable e2) { Log.error("Transaction callback error", e2); } } }
private void persist() { while (!Thread.interrupted()) { try { persistData(); } catch (Exception e1) { Log.error("Failed to persist data!", e1); } if (data.active.get()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } else { try { persistData(); } catch (Exception e1) { Log.error("Failed to persist data!", e1); } return; } } }
/** * ACID transactional semantics:<br> * - Atomicity with automatic rollback in case of exception,<br> * - Consistency - only with constraints enforced programmatically inside transaction,<br> * - Isolation is serializable (with global lock),<br> * - Durability through on-commit callbacks.<br> */ public void transaction(Runnable transaction, boolean readOnly, Callback<Void> txCallback) { globalLock(); data.txIdCounter.set(data.ids.get()); data.txChanges.clear(); data.txInsertions.clear(); data.txReadonly.set(readOnly); data.insideTx.set(true); boolean success = false; try { transaction.run(); success = true; } catch (Throwable e) { if (SuccessException.isSuccess(e)) { success = true; throw U.rte(e); } else { Log.error("Error in transaction, rolling back", e); txRollback(); if (txCallback != null) { txCallback.onDone(null, e); txCallback = null; } } } finally { data.txChanges.clear(); data.txInsertions.clear(); data.insideTx.set(false); if (persistor != null) { if (success && txCallback != null) { data.txCallbacks.add(txCallback); } } else { if (success && txCallback != null) { txCallback.onDone(null, null); } } globalUnlock(); } }
public void invalidateCache() { Log.debug("Invalidating Mustache cache"); mustacheCache.invalidateAll(); lambdaCache.invalidateAll(); }
private void update_(long id, Object record, boolean reflectRelChanges, boolean checkSecurity) { failIfReadonlyTx(); validateId(id); Rec old = data.data.get(id); Object entity = obj(old); if (checkSecurity) { secureUpdate(entity); } // Optimistic concurrency control through the "version" property Long oldVersion = U.or(Beany.getPropValueOfType(entity, VERSION, Long.class, null), 0L); Long recordVersion = U.or(Beany.getPropValueOfType(record, VERSION, Long.class, null), 0L); occErrorIf( !U.eq(oldVersion, recordVersion), "Concurrent modification occured while updating the object with ID=%s!", id); Beany.setId(record, id); if (!sudo && checkSecurity) { boolean canUpdate = false; for (Prop prop : Beany.propertiesOf(record)) { if (!Secure.getPropertyPermissions(username(), entity.getClass(), entity, prop.getName()) .change) { prop.set(record, prop.get(entity)); } else { canUpdate = true; } } U.secure( canUpdate, "Not enough privileges to update any column of %s!", entity.getClass().getSimpleName()); } // Optimistic concurrency control through the "version" property if (Beany.hasProperty(record, VERSION)) { Beany.setPropValue(record, VERSION, oldVersion + 1); } if (checkSecurity) { secureUpdate(record); } if (Beany.hasProperty(record, LAST_UPDATED_BY)) { Beany.setPropValue(record, LAST_UPDATED_BY, username()); } if (Beany.hasProperty(record, LAST_UPDATED_ON)) { Beany.setPropValue(record, LAST_UPDATED_ON, new Date()); } boolean updated = data.data.replace(id, old, rec(record)); occErrorIf( !updated, "Concurrent modification occured while updating the object with ID=%s!", id); if (data.insideTx.get()) { data.txChanges.putIfAbsent(id, old); } if (old == null) { throw new IllegalStateException("Cannot update non-existing record with ID=" + id); } if (reflectRelChanges) { updateChangesFromRels(record); } data.lastChangedOn.set(System.currentTimeMillis()); Log.audit("Updated DB record", "id", id); }
public void loadFrom(InputStream in) { globalLock(); try { data.data.clear(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = reader.readLine(); byte[] bytes = line.getBytes(); U.must(line != null, "Missing meta-data at the first line in the database file!"); Map<String, Object> meta = U.map(); data.serializer.deserialize(bytes, meta); Log.info( "Database meta-data", META_TIMESTAMP, meta.get(META_TIMESTAMP), META_UPTIME, meta.get(META_UPTIME)); while ((line = reader.readLine()) != null) { bytes = line.getBytes(); Map<String, Object> map = U.map(); data.serializer.deserialize(bytes, map); Object idObj = map.get(ID); U.must(idObj != null, "Found DB record without ID: %s", line); long id = Cls.convert(idObj, Long.class); String className = ((String) map.get("_class")); String[] nameParts = className.split("\\."); String simpleName = nameParts[nameParts.length - 1]; List<Class<?>> classes = Scan.byName(simpleName, null, null); if (classes.size() == 1) { Class<?> type = classes.get(0); data.data.put(id, new Rec(type, bytes)); if (id > data.ids.get()) { data.ids.set(id); } } else { if (classes.isEmpty()) { Log.error("Cannot find the class of a DB record!", "id", id, "class", className); } else { Log.error("Found more than 1 class of a DB record!", "id", id, "class", className); } } } data.prevData = new ConcurrentSkipListMap<Long, Rec>(data.data); reader.close(); } catch (IOException e) { throw new RuntimeException("Cannot load database!", e); } finally { globalUnlock(); } }
private Mustache compileIfChanged(String filename, boolean partial) { Mustache template = mustacheCache.getIfPresent(filename); if (template == null) { String desc = partial ? "partial" : "template"; Log.debug("Compiling Mustache " + desc, "name", filename); Res res = getResource(filename, partial); template = customCompile(filename, res); res.onChange( "mustache", new Runnable() { @Override public void run() { invalidateCache(); } }) .trackChanges(); mustacheCache.put(filename, template); } return template; }