org.jooby.requery.Requery Maven / Gradle / Ivy
Show all versions of jooby-requery Show documentation
/**
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
*
* 1. Definitions.
*
* "License" shall mean the terms and conditions for use, reproduction,
* and distribution as defined by Sections 1 through 9 of this document.
*
* "Licensor" shall mean the copyright owner or entity authorized by
* the copyright owner that is granting the License.
*
* "Legal Entity" shall mean the union of the acting entity and all
* other entities that control, are controlled by, or are under common
* control with that entity. For the purposes of this definition,
* "control" means (i) the power, direct or indirect, to cause the
* direction or management of such entity, whether by contract or
* otherwise, or (ii) ownership of fifty percent (50%) or more of the
* outstanding shares, or (iii) beneficial ownership of such entity.
*
* "You" (or "Your") shall mean an individual or Legal Entity
* exercising permissions granted by this License.
*
* "Source" form shall mean the preferred form for making modifications,
* including but not limited to software source code, documentation
* source, and configuration files.
*
* "Object" form shall mean any form resulting from mechanical
* transformation or translation of a Source form, including but
* not limited to compiled object code, generated documentation,
* and conversions to other media types.
*
* "Work" shall mean the work of authorship, whether in Source or
* Object form, made available under the License, as indicated by a
* copyright notice that is included in or attached to the work
* (an example is provided in the Appendix below).
*
* "Derivative Works" shall mean any work, whether in Source or Object
* form, that is based on (or derived from) the Work and for which the
* editorial revisions, annotations, elaborations, or other modifications
* represent, as a whole, an original work of authorship. For the purposes
* of this License, Derivative Works shall not include works that remain
* separable from, or merely link (or bind by name) to the interfaces of,
* the Work and Derivative Works thereof.
*
* "Contribution" shall mean any work of authorship, including
* the original version of the Work and any modifications or additions
* to that Work or Derivative Works thereof, that is intentionally
* submitted to Licensor for inclusion in the Work by the copyright owner
* or by an individual or Legal Entity authorized to submit on behalf of
* the copyright owner. For the purposes of this definition, "submitted"
* means any form of electronic, verbal, or written communication sent
* to the Licensor or its representatives, including but not limited to
* communication on electronic mailing lists, source code control systems,
* and issue tracking systems that are managed by, or on behalf of, the
* Licensor for the purpose of discussing and improving the Work, but
* excluding communication that is conspicuously marked or otherwise
* designated in writing by the copyright owner as "Not a Contribution."
*
* "Contributor" shall mean Licensor and any individual or Legal Entity
* on behalf of whom a Contribution has been received by Licensor and
* subsequently incorporated within the Work.
*
* 2. Grant of Copyright License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* copyright license to reproduce, prepare Derivative Works of,
* publicly display, publicly perform, sublicense, and distribute the
* Work and such Derivative Works in Source or Object form.
*
* 3. Grant of Patent License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* (except as stated in this section) patent license to make, have made,
* use, offer to sell, sell, import, and otherwise transfer the Work,
* where such license applies only to those patent claims licensable
* by such Contributor that are necessarily infringed by their
* Contribution(s) alone or by combination of their Contribution(s)
* with the Work to which such Contribution(s) was submitted. If You
* institute patent litigation against any entity (including a
* cross-claim or counterclaim in a lawsuit) alleging that the Work
* or a Contribution incorporated within the Work constitutes direct
* or contributory patent infringement, then any patent licenses
* granted to You under this License for that Work shall terminate
* as of the date such litigation is filed.
*
* 4. Redistribution. You may reproduce and distribute copies of the
* Work or Derivative Works thereof in any medium, with or without
* modifications, and in Source or Object form, provided that You
* meet the following conditions:
*
* (a) You must give any other recipients of the Work or
* Derivative Works a copy of this License; and
*
* (b) You must cause any modified files to carry prominent notices
* stating that You changed the files; and
*
* (c) You must retain, in the Source form of any Derivative Works
* that You distribute, all copyright, patent, trademark, and
* attribution notices from the Source form of the Work,
* excluding those notices that do not pertain to any part of
* the Derivative Works; and
*
* (d) If the Work includes a "NOTICE" text file as part of its
* distribution, then any Derivative Works that You distribute must
* include a readable copy of the attribution notices contained
* within such NOTICE file, excluding those notices that do not
* pertain to any part of the Derivative Works, in at least one
* of the following places: within a NOTICE text file distributed
* as part of the Derivative Works; within the Source form or
* documentation, if provided along with the Derivative Works; or,
* within a display generated by the Derivative Works, if and
* wherever such third-party notices normally appear. The contents
* of the NOTICE file are for informational purposes only and
* do not modify the License. You may add Your own attribution
* notices within Derivative Works that You distribute, alongside
* or as an addendum to the NOTICE text from the Work, provided
* that such additional attribution notices cannot be construed
* as modifying the License.
*
* You may add Your own copyright statement to Your modifications and
* may provide additional or different license terms and conditions
* for use, reproduction, or distribution of Your modifications, or
* for any such Derivative Works as a whole, provided Your use,
* reproduction, and distribution of the Work otherwise complies with
* the conditions stated in this License.
*
* 5. Submission of Contributions. Unless You explicitly state otherwise,
* any Contribution intentionally submitted for inclusion in the Work
* by You to the Licensor shall be under the terms and conditions of
* this License, without any additional terms or conditions.
* Notwithstanding the above, nothing herein shall supersede or modify
* the terms of any separate license agreement you may have executed
* with Licensor regarding such Contributions.
*
* 6. Trademarks. This License does not grant permission to use the trade
* names, trademarks, service marks, or product names of the Licensor,
* except as required for reasonable and customary use in describing the
* origin of the Work and reproducing the content of the NOTICE file.
*
* 7. Disclaimer of Warranty. Unless required by applicable law or
* agreed to in writing, Licensor provides the Work (and each
* Contributor provides its Contributions) on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied, including, without limitation, any warranties or conditions
* of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
* PARTICULAR PURPOSE. You are solely responsible for determining the
* appropriateness of using or redistributing the Work and assume any
* risks associated with Your exercise of permissions under this License.
*
* 8. Limitation of Liability. In no event and under no legal theory,
* whether in tort (including negligence), contract, or otherwise,
* unless required by applicable law (such as deliberate and grossly
* negligent acts) or agreed to in writing, shall any Contributor be
* liable to You for damages, including any direct, indirect, special,
* incidental, or consequential damages of any character arising as a
* result of this License or out of the use or inability to use the
* Work (including but not limited to damages for loss of goodwill,
* work stoppage, computer failure or malfunction, or any and all
* other commercial damages or losses), even if such Contributor
* has been advised of the possibility of such damages.
*
* 9. Accepting Warranty or Additional Liability. While redistributing
* the Work or Derivative Works thereof, You may choose to offer,
* and charge a fee for, acceptance of support, warranty, indemnity,
* or other liability obligations and/or rights consistent with this
* License. However, in accepting such obligations, You may act only
* on Your own behalf and on Your sole responsibility, not on behalf
* of any other Contributor, and only if You agree to indemnify,
* defend, and hold each Contributor harmless for any liability
* incurred by, or claims asserted against, such Contributor by reason
* of your accepting any such warranty or additional liability.
*
* END OF TERMS AND CONDITIONS
*
* APPENDIX: How to apply the Apache License to your work.
*
* To apply the Apache License to your work, attach the following
* boilerplate notice, with the fields enclosed by brackets "{}"
* replaced with your own identifying information. (Don't include
* the brackets!) The text should be enclosed in the appropriate
* comment syntax for the file format. We also recommend that a
* file or class name and description of purpose be included on the
* same "printed page" as the copyright notice for easier
* identification within third-party archives.
*
* Copyright 2014 Edgar Espina
*
* 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 org.jooby.requery;
import com.google.inject.name.Names;
import io.requery.sql.KotlinEntityDataStore;
import static java.util.Objects.requireNonNull;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.inject.Provider;
import javax.sql.DataSource;
import org.jooby.Env;
import org.jooby.Env.ServiceKey;
import org.jooby.Jooby.Module;
import org.jooby.jdbc.Jdbc;
import org.slf4j.bridge.SLF4JBridgeHandler;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.util.Types;
import com.typesafe.config.Config;
import io.requery.EntityStore;
import io.requery.TransactionListener;
import io.requery.async.CompletableEntityStore;
import io.requery.async.CompletionStageEntityStore;
import io.requery.meta.EntityModel;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.reactor.ReactorEntityStore;
import io.requery.sql.Configuration;
import io.requery.sql.ConfigurationBuilder;
import io.requery.sql.EntityDataStore;
import io.requery.sql.EntityStateListener;
import io.requery.sql.SchemaModifier;
import io.requery.sql.StatementListener;
import io.requery.sql.TableCreationMode;
/**
* requery
*
* Safe, clean and efficient database access via
* Requery.
*
*
* usage
*
* {@code
* {
* use(new Jdbc());
*
* use(new Requery(Models.DEFAULT));
*
* get("/people", () -> {
* EntityStore store = require(EntityStore.class);
* return store.select(Person.class)
* .where(Person.ID.eq(req.param("id").intValue()))
* .get()
* .first();
* });
* }
* }
*
*
* This module requires a {@link DataSource} connection. That's why you also need the
* {@link Jdbc} module.
*
*
* code generation
*
* maven
*
* We do provide code generation via Maven profile. All you have to do is to write a
* requery.activator
file inside the src/etc
folder. File presence
* triggers Requery annotation processor and generated contents.
*
*
*
* Generated content can be found at: target/generated-sources
. You can change the
* default output location by setting the build property requery.output
in your
* pom.xml
.
*
*
* gradle
*
* Please refer to requery
* documentation for Gradle.
*
*
* schema generation
*
* {@code
* {
* use(new Requery(Models.DEFAULT)
* .schema(TableCreationMode.DROP_CREATE)
* );
* }
* }
*
*
* Optionally, schema generation could be set from .conf file via requery.schema
* property.
*
*
* listeners
*
* {@code
* public class MyListener implements EntityStateListener {
* @Inject
* public MyListener(Dependency dep) {
* this.dep = dep;
* }
*
* ...
* }
*
* {
* use(new Requery(Models.DEFAULT)
* .entityStateListener(MyListener.class)
* );
* }
* }
*
*
* Support for {@link TransactionListener} and {@link StatementListener} is also provided:
*
*
* {@code
* {
* use(new Requery(Models.DEFAULT)
* .statementListener(MyStatementListener.class)
* .transactionListener(TransactionListener.class)
* );
* }
* }
*
*
* You can add as many listener as you need. Each listener will be created by Guice
*
*
* Type-Safe injection
*
* If you love DAO
like classes, we are happy to tell you that it you easily inject
* type-safe {@link EntityStore}:
*
*
* {@code
*
* public class PersonDAO {
* private EntityStore<Persistable, Person> store;
*
* @Inject
* public PersonDAO(EntityStore<
*
*
* Please note we don't inject a raw
{@link EntityStore}. Instead we ask for a
* Person
{@link EntityStore}. You can safely inject a {@link EntityStore} per each of
* your domain objects.
*
*
* async and reactive idioms
*
*
* Rxjava:
*
*
* {@code
* {
* use(Requery.reactive(Models.DEFAULT));
*
* get("/", () -> {
* ReactiveEntityStore store = require(ReactiveEntityStore.class);
* // work with reactive store
* });
* }
* }
*
*
* Reactor:
*
*
* {@code
* {
* use(Requery.reactor(Models.DEFAULT));
*
* get("/", () -> {
* ReactorEntityStore store = require(ReactorEntityStore.class);
* // work with reactor store
* });
* }
* }
*
*
* Java 8:
*
*
* {@code
* {
* use(Requery.completionStage(Models.DEFAULT));
*
* get("/", () -> {
* CompletionStageEntityStore store = require(CompletionStageEntityStore.class);
* // work with reactor store
* });
* }
* }
*
* advanced configuration
*
* Advanced configuration is available via callback function:
*
*
* {@code
* {
* use(new Requery(Models.DEFAULT)
* .doWith(builder -> {
* builder.useDefaultLogging();
* ....
* })
* );
* }
* }
*
* @author edgar
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class Requery implements Module {
static {
if (!SLF4JBridgeHandler.isInstalled()) {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
}
}
private String name;
private final EntityModel model;
private TableCreationMode schema;
private final Function store;
private final Class> storeType;
private List states = new LinkedList<>();
private List statements = new LinkedList<>();
private List transactions = new LinkedList<>();
private BiConsumer callback;
private Requery(final String name, final Class> storeType, final EntityModel model,
final Function store) {
this.name = name;
this.storeType = storeType;
this.model = model;
this.store = store;
}
/**
* Creates a new {@link Requery} module.
*
* @param name Database name.
* @param model Entity model.
*/
public Requery(final String name, final EntityModel model) {
this(name, EntityStore.class, model, c -> new EntityDataStore<>(c));
}
/**
* Creates a new {@link Requery} module.
*
* @param model Entity model.
*/
public Requery(final EntityModel model) {
this("db", model);
}
/**
* Advanced configuration callback:
*
* {@code
*
* use(new Requery(Models.DEFAULT)
* .doWith((conf, builder) -> {
* builder.useDefaultLogging();
* });
*
* }
*
* @param configurer Configurer callback.
* @return This module.
*/
public Requery doWith(final BiConsumer configurer) {
this.callback = requireNonNull(configurer, "Configurer callback required.");
return this;
}
/**
* Advanced configuration callback:
*
* {@code
*
* use(new Requery(Models.DEFAULT)
* .doWith(builder -> {
* builder.useDefaultLogging();
* })
* );
*
* }
*
* @param configurer Configurer callback.
* @return This module.
*/
public Requery doWith(final Consumer configurer) {
requireNonNull(configurer, "Configurer callback required.");
return doWith((c, b) -> configurer.accept(b));
}
/**
* Run the give schema command at application startup time.
*
* @param schema Command to run.
* @return This module.
*/
public Requery schema(final TableCreationMode schema) {
this.schema = schema;
return this;
}
/**
* Add an {@link EntityStateListener}. The listener will be created by Guice.
*
* @param listener Guice entity listener.
* @return This module.
*/
public Requery entityStateListener(final Class extends EntityStateListener>> listener) {
this.states.add(listener);
return this;
}
/**
* Add an {@link StatementListener}. The listener will be created by Guice.
*
* @param listener Guice statement listener.
* @return This module.
*/
public Requery statementListener(final Class extends StatementListener> listener) {
this.statements.add(listener);
return this;
}
/**
* Add an {@link TransactionListener}. The listener will be created by Guice.
*
* @param listener Guice transaction listener.
* @return This module.
*/
public Requery transactionListener(final Class extends TransactionListener> listener) {
this.transactions.add(listener);
return this;
}
@Override
public void configure(final Env env, final Config conf, final Binder binder) {
Key dskey = Key.get(DataSource.class, Names.named(name));
DataSource dataSource = env.get(dskey)
.orElseThrow(() -> new NoSuchElementException("DataSource missing: " + dskey));
ConfigurationBuilder builder = new ConfigurationBuilder(dataSource, model);
if (callback != null) {
callback.accept(conf, builder);
}
Configuration configuration = builder.build();
Object newStore = store.apply(configuration);
ServiceKey keys = env.serviceKey();
Consumer bind = k -> binder.bind((Key) k).toInstance(newStore);
/**
* For each model class we publish a type-safe EntityStore, so users can easily inject stores
* for specific types.
*/
model.getTypes().forEach(it -> {
// Person(result) extends AbstractPerson implements Persistable (base)
Class target = it.getClassType();
Class base = target.getInterfaces()[0];
// bind: Store
bind.accept(Key.get(Types.newParameterizedType(storeType, target)));
// bind: Store
bind.accept(Key.get(Types.newParameterizedType(storeType, base, target)));
});
keys.generate(storeType, model.getName(), bind);
env.onStart(registry -> {
schema(conf, schema, schema -> new SchemaModifier(dataSource, model).createTables(schema));
states
.forEach(t -> builder.addEntityStateListener((EntityStateListener) registry.require(t)));
statements
.forEach(t -> builder.addStatementListener((StatementListener) registry.require(t)));
transactions.forEach(t -> builder
.addTransactionListenerFactory(() -> (TransactionListener) registry.require(t)));
});
}
/**
* Creates a Requery module with RxJava data store.
*
* {@code
* {
* use(Requery.reactive(Models.DEFAULT));
*
* get("/", () -> {
* ReactiveEntityStore store = require(ReactiveEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param name Database name.
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery reactive(final String name, final EntityModel model) {
return new Requery(name, ReactiveEntityStore.class, model,
conf -> ReactiveSupport.toReactiveStore(new EntityDataStore<>(conf)));
}
/**
* Creates a Requery module with RxJava data store.
*
* {@code
* {
* use(Requery.reactive(Models.DEFAULT));
*
* get("/", () -> {
* ReactiveEntityStore store = require(ReactiveEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery reactive(final EntityModel model) {
return reactive("db", model);
}
/**
* Creates a Requery module with Reactor data store.
*
* {@code
* {
* use(Requery.reactor(Models.DEFAULT));
*
* get("/", () -> {
* ReactorEntityStore store = require(ReactorEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param name Database name.
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery reactor(final String name, final EntityModel model) {
return new Requery(name, ReactorEntityStore.class, model,
conf -> new ReactorEntityStore<>(new EntityDataStore<>(conf)));
}
/**
* Creates a Requery module with Reactor data store.
*
* {@code
* {
* use(Requery.reactor(Models.DEFAULT));
*
* get("/", () -> {
* ReactorEntityStore store = require(ReactorEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery reactor(final EntityModel model) {
return reactor("db", model);
}
/**
* Creates an async Requery module with Java 8 data store.
*
* {@code
* {
* use(Requery.completionStage(Models.DEFAULT));
*
* get("/", () -> {
* CompletionStageEntityStore store = require(CompletionStageEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param name Database name.
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery completionStage(final String name, final EntityModel model) {
return new Requery(name, CompletionStageEntityStore.class, model,
conf -> new CompletableEntityStore(new EntityDataStore<>(conf)));
}
/**
* Creates an async Requery module with Java 8 data store.
*
* {@code
* {
* use(Requery.completionStage(Models.DEFAULT));
*
* get("/", () -> {
* CompletionStageEntityStore store = require(CompletionStageEntityStore.class);
* // work with reactive store
* });
* }
* }
*
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery completionStage(final EntityModel model) {
return completionStage("db", model);
}
/**
* Creates a Kotlin Requery module.
*
* {@code
* {
* use(Requery.kotlin(Models.DEFAULT));
*
* get("/", () -> {
* KotlinEntityDataStore store = require(KotlinEntityDataStore.class);
* // work with kotlin store
* });
* }
* }
*
* @param name Database name.
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery kotlin(final String name, final EntityModel model) {
return new Requery(name, KotlinEntityDataStore.class, model,
conf -> new KotlinEntityDataStore<>(conf));
}
/**
* Creates a Kotlin Requery module.
*
* {@code
* {
* use(Requery.kotlin(Models.DEFAULT));
*
* get("/", () -> {
* KotlinEntityDataStore store = require(KotlinEntityDataStore.class);
* // work with kotlin store
* });
* }
* }
*
* @param model Entity model.
* @return A new {@link Requery} module.
*/
public static Requery kotlin(final EntityModel model) {
return kotlin("db", model);
}
private void schema(final Config conf, final TableCreationMode schema,
final Consumer callback) {
if (schema != null) {
callback.accept(schema);
}
if (conf.hasPath("requery.schema")) {
callback.accept(TableCreationMode.valueOf(conf.getString("requery.schema").toUpperCase()));
}
}
}