I have a spring project, and I need to use cache in my service class, like this:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
#Service
public class WebAuthnServer {
private final Cache<String, RegistrationRequest> registerRequestStorage = newCache();
private static <K, V> Cache<K, V> newCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
}
public Either<String, RegistrationRequest> startRegistration(String username, String displayName, String credentialNickname, boolean requireResidentKey) {
if (userStorage.getRegistrationsByUsername(username).isEmpty()) {
RegistrationRequest request = new RegistrationRequest(...);
registerRequestStorage.put(request.getRequestId(), request);
} else {
return new Left("The username \"" + username + "\" is already registered.");
}
}
}
I have registerRequestStorage cache, and I put some data in cache using the method startRegistration. But when I try to get this data in another method, the cache is empty.
public Either<List<String>, SuccessfulRegistrationResult> finishRegistration(String responseJson) {
RegistrationResponse response = null;
try {
response = jsonMapper.readValue(responseJson, RegistrationResponse.class);
} catch (IOException e) {
return Left.apply(Arrays.asList("Registration failed!", "Failed to decode response object.", e.getMessage()));
}
RegistrationRequest request = registerRequestStorage.getIfPresent(response.getRequestId());
registerRequestStorage.invalidate(response.getRequestId());
if (request == null) {
logger.info("fail finishRegistration responseJson: {}", responseJson);
return Left.apply(Arrays.asList("Registration failed!", "No such registration in progress."));
} else {
Try<RegistrationResult> registrationTry = rp.finishRegistration(
request.getPublicKeyCredentialCreationOptions(),
response.getCredential(),
Optional.empty()
);
}
}
Related
I'm creating a component class that overrides a reactive method that calls another microservice "uaa" that validates a token, but when I verify that the token is invalid I throw an exception, but that exception does not catch in my exception controller handler
here is my component class
#Slf4j
#Component
#RequiredArgsConstructor
public class AuthFilter implements GlobalFilter {
private final JwtTokenProviderService jwtTokenProviderService;
private final TokenStatusDaoService tokenStatusDaoService;
private final WebClient webClient;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("something in the way");
List<String> headers = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
if(CollectionUtils.isEmpty(headers)) {
log.trace("Request came without token");
return chain.filter(exchange);
} else {
String authToken = headers.get(0);
log.trace("Request holds a token");
log.debug("Check if token has expired ...");
if(jwtTokenProviderService.isTokenExpired(authToken)) {
log.debug("Token has expired will throw an error");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_HAS_EXPIRED, "Token has expired");
}else {
log.debug("Check if token is valid and already saved");
String userId = jwtTokenProviderService.getClaimsFromToken(authToken).get(SecurityUtils.IDENTIFIER_KEY).toString();
if(!tokenStatusDaoService.exists(TokenStatusSpecification.withToken(authToken).and(TokenStatusSpecification.withUserId(Long.parseLong(userId))))) {
return webClient.get()
.uri("http://uaa", uriBuilder -> uriBuilder
.path("/validate-token")
.queryParam("token", authToken).build()).retrieve()
.bodyToMono(TokenValidationGetResource.class)
.map(tokenValidationGetResource -> {
if (!tokenValidationGetResource.isValid()) {
log.debug("token is not valid");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_NOT_VALID, "Token is not valid");
} else {
log.debug("token is valid");
TokenStatusEntity tokenStatusEntity;
try {
tokenStatusEntity = tokenStatusDaoService.findOne(TokenStatusSpecification.withUserId(Long.parseLong(userId)));
} catch (Exception e) {
log.debug("No token defined for user: {}. Will save a new one ...", userId);
tokenStatusEntity = new TokenStatusEntity();
}
tokenStatusEntity.setToken(authToken);
tokenStatusEntity.setUserId(Long.parseLong(userId));
tokenStatusEntity.setStatus(TokenStatusEnum.VALID);
tokenStatusDaoService.save(tokenStatusEntity);
log.debug("Token status entity: {}", tokenStatusEntity);
return exchange;
}
}).flatMap(chain::filter);
} else {
log.debug("Token exists in DB");
return chain.filter(exchange);
}
}
}
}
}
and here is my exception controller handler:
#ControllerAdvice
public class ExceptionControllerImpl implements ExceptionController {
#Override
#ExceptionHandler({
AuthorizationForbiddenException.class
})
public ResponseEntity<ErrorDetailResource> handleGenericExceptions(
AbstractBaseException e, HttpServletRequest request) {
ErrorDetailResource errorDetailResource = new ErrorDetailResource();
errorDetailResource.setTimestamp(Instant.now().toEpochMilli());
errorDetailResource.setTitle(e.getTitle().toString());
errorDetailResource.setCode(e.getTitle().getCode());
errorDetailResource.setDeveloperMessage(e.getClass().getName());
errorDetailResource.setStatus(e.getStatus().value());
errorDetailResource.setDetail(e.getMessage());
return new ResponseEntity<>(errorDetailResource, e.getStatus());
}
}
Hello Those exceptions are thrown on a mono method in a reactive manner, so they can not be caught by controller advice, instead of doing that create a class which will extends the abstract class AbstractErrorWebExceptionHandler
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes globalErrorAttributes,
ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(globalErrorAttributes, new WebProperties.Resources(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());
Throwable error = null;
// here is your abstract base exception
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
HttpStatus statusCode = baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
return ServerResponse.status(statusCode)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
And of course do not forget to add DefaultErrorAttributes
#Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Throwable error = null;
// here is your abstract base exception
// cast the error to your exception class
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
Map<String, Object> errorResources = new HashMap<>();
// Define the attribute that you want to return in response body
errorResources.put("attribute1", Instant.now().toEpochMilli());
errorResources.put("attribute2", baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR);
return errorResources;
}
}
want the execution will wait until i get the token back. this class has few more methods which will also wait until the token received.
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
currentSubject = subject;
currentCallbackHandler = callbackHandler;
currentSharedState = (Map<String, Object>) sharedState;
currentOptions = (Map<String, Object>) options;
success = false;
System.out.println("kousik level 0.1");
SAMLToken token= null;
WSSUtilFactory factory = null;
try{
synchronized (token) {
while (token == null){
wait(100);
factory = WSSUtilFactory.getInstance();
token = factory.getSaml20Token();
}
if(token != null)
notifyAll();
}
}
catch(Exception e){
e.printStackTrace();
}
}
If the problem is that you cannot use synchronize(null), I think you can try use an array instead, as the code shows below.
Annother problem is that you use the synchronize on a local variable, there is no race condition with other methods.
SAMLToken token = null;
WSSUtilFactory factory = null;
final SAMLToken[] ref = new SAMLToken[] {token};
try {
synchronized (ref) {
while (ref[0] == null) {
wait(100);
factory = WSSUtilFactory.getInstance();
ref[0] = factory.getSaml20Token();
}
if (ref[0] != null) {
notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
}
You can use awaitility.
public void main() {
String token = waitForTokenAtMostSeconds(30);
}
private String waitForTokenAtMostSeconds(int seconds) {
String token = waitAtMostSeconds(seconds, () -> factory.getSaml20Token());
return Objects.requireNonNull(token, "Cannot find token");
}
private static <T> T waitAtMostSeconds(int seconds, Callable<T> supplier) {
return Awaitility.await().atMost(seconds, TimeUnit.SECONDS)
.until(supplier, Objects::nonNull);
}
I want to use this webflux client code to send POST requests with reply and without reply. I tried this code implementation:
public class RestClientBuilder {
private String token;
private String username;
private String password;
private URL gatewayUrl;
private SslContextBuilder sslContextBuilder;
public static RestClientBuilder builder() {
return new RestClientBuilder();
}
public RestClientBuilder token(String token) {
this.token = validateAndTrim(token, "Token");
return this;
}
public RestClientBuilder usernamePassword(String username, String password) {
this.username = validateAndTrim(username, "Username");
this.password = validateAndTrim(password, "Password");
return this;
}
private String validateAndTrim(String value, final String parameter) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException(parameter + " is empty");
}
return value.trim();
}
public RestClientBuilder gatewayUrl(String gatewayUrl) {
String urlSt = validateAndTrim(gatewayUrl, "Gateway URL");
try {
this.gatewayUrl = new URL(urlSt);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed URL: " + urlSt, e);
}
return this;
}
public RestClientBuilder truststore(File truststoreFile) {
getSslContextBuilder().trustManager(truststoreFile);
return this;
}
public RestClientBuilder sslCertificate(File keyCertChainFile, File keyFile, String keyPassword) {
getSslContextBuilder().keyManager(keyCertChainFile, keyFile, keyPassword);
return this;
}
public RestClient build() throws SSLException {
SslContext sslContext = sslContextBuilder != null ? sslContextBuilder.build() : null;
return new RestClient(gatewayUrl.toString(), token, username, password, sslContext);
}
private SslContextBuilder getSslContextBuilder() {
if (sslContextBuilder == null) {
sslContextBuilder = SslContextBuilder.forClient();
}
return sslContextBuilder;
}
}
Implementation of the rest client:
public class RestClient {
private WebClient client;
private String gatewayUrl;
public RestClient(String gatewayUrl, String token, String username, String password, SslContext sslContext) {
this.gatewayUrl = gatewayUrl;
WebClient.Builder builder = WebClient.builder().baseUrl(gatewayUrl);
if (sslContext != null) {
HttpClient httpClient = HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient);
builder.clientConnector(httpConnector);
}
if (username != null && password != null) {
builder.filter(basicAuthentication(username, password));
}
client = builder.build();
}
public Mono<Void> executeOnly(ReportRequest transaction) {
Mono<ReportRequest> transactionMono = Mono.just(transaction);
return client.post().uri(gatewayUrl)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, ReportRequest.class)
.retrieve()
.bodyToMono(Void.class);
}
}
Make remote calls:
public class ReportingProcessor {
private String URL2 = "......";
public void collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
// .token(contract.getTerminal_token())
// .usernamePassword("user", "password")
// .truststore(new File("server.pem"))
// .sslCertificate(new File("client.pem"), new File("clientKey.p8"), "secret")
.build();
} catch (SSLException e) {
e.printStackTrace();
}
Mono<Void> result = client.executeOnly(report);
Void response = result.block();
}
When I remove Void response = result.block(); the request is not send. I Can't find why. Can you give me some advice how to make the client code working without using block().
Whenever you work with Spring-webflux you have to keep one thing in mind. i.e You don't have to break your chain. because it is necessary to, someone should call subscribe on your chain. as it works on RXJava specification.
if you break the chain then you have to call block() which not recommended.
you have to modify your code in the below manner.
Let's Consider you have a handler which is making a call to your collectEnvironmentData() method and your method is making a call to remote service.
public Mono<ServerResponse> handelerMethod(ServerRequest request){
return collectEnvironmentData().flatMap(aVoid -> ServerResponse.ok().build());
}
your method should be modified to
public Mono<Void> collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
// .token(contract.getTerminal_token())
// .usernamePassword("user", "password")
// .truststore(new File("server.pem"))
// .sslCertificate(new File("client.pem"), new File("clientKey.p8"),
//"secret").build();
} catch (SSLException e) {
e.printStackTrace();
}
return client.executeOnly(report);
}
Change your implementation in the above manner, hope it will work.
How I would implement your method is:
public Mono<Void> executeOnly(ReportRequest transaction) {
Mono<ReportRequest> transactionMono = Mono.just(transaction);
return client.post().uri(gatewayUrl)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transaction, ReportRequest.class)
.exchange()
.then();
}
And then I would use it as follows:
client.executeOnly(report).subscribe()
Change the method return type to Mono<Void> for end to end streaming.
public void collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
.build();
} catch (SSLException e) {
e.printStackTrace();
}
return client.executeOnly(report);
}
Or you can also subscribe the Mono
client.executeOnly(report).subscribe();
I have used Spring state-machine in quite a complex scenario. I will explain my problem with the simplest part of the SM. Refer below image. This is my main state machine
The state circled in red points to the following sub-machine
So, as you can see, I have 3 actions. sendBasicTemplate, timeoutLogAction and processBasicTemplateReply. I will provide the related code segments and my configuration below.
What I have observed during this process is that the state-machines created by the factory resides in memory always. There's some reference to it which i cannot think of.
Is it that the SM doesn't stop or is there anything I'm doing wrong? Here's my code.
Configuration class
#Configuration #EnableStateMachineFactory public class CambodiaStateMachine extends StateMachineConfigurerAdapter<String, String> {
#Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
#Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.machineId("cambodia")
.autoStartup(true)
.listener(listener()); }
#Bean
public StateMachineListener<String, String> listener() {
return new StateMachineListenerAdapter<String, String>() {
#Override
public void stateChanged(State<String, String> from, State<String, String> to) {
System.out.println("State change to " + to.getId());
}
};
}
#Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new UmlStateMachineModelFactory("classpath:stm/model.uml");
}
}
Methods : 1. This is how my events are fed to the machine and where new SM instances are made. I take my events from a queue
#RabbitListener(bindings = #QueueBinding(value = #Queue(value = "sims.events.mq", durable = "true"), exchange = #Exchange(type = ExchangeTypes.TOPIC, value = "sims.events.mq.xch", ignoreDeclarationExceptions = "true", durable = "true"), key = "events"))
public void process(GenericMessage<String> message) {
try {
String imei = (String) message.getHeaders().get("imei");
Subscriber subscriber = subscriberService.findSubscriber(imei);
// quickly create 'new' state machine
StateMachine<String, String> stateMachine = factory.getStateMachine();
stateMachine.addStateListener(new CompositeStateMachineListener<String, String>() {
#Override
public void stateContext(StateContext<String, String> arg0) {
String user = (String) arg0.getExtendedState().getVariables().get("imei");
if (user == null) {
return;
}
log.info(arg0.getStage().toString() + "**********" + stateMachine.getState());
try {
redisStateMachinePersister.persist(arg0.getStateMachine(), "testprefixSw:" + user);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
});
// restore from persistent
String user = (String) message.getHeaders().get("imei");
log.info(user);
// attempt restoring only if key is exist
if (redisTemplate.hasKey("testprefixSw:" + user)) {
System.out.println("************************ prefix exists...restoring");
resetStateMachineFromStore(stateMachine, user);
} else {
stateMachine.start();
System.out.println("************************ No prefix");
}
log.info("Payload == > " + message.getPayload());
try {
stateMachine.getExtendedState().getVariables().put("imei", user);
stateMachine.getExtendedState().getVariables().put("fromState", stateMachine.getState().getId());
stateMachine.getExtendedState().getVariables().put("eventName", message.getPayload());
if(null!= message.getHeaders().get("templates"))
stateMachine.getExtendedState().getVariables().put("templates", message.getHeaders().get("templates"));
if(null!= message.getHeaders().get("ttl"))
stateMachine.getExtendedState().getVariables().put("ttl", message.getHeaders().get("ttl"));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// check if state is properly restored...
log.info("Current State " + stateMachine.getState().toString());
feedMachine(stateMachine, user, message);
log.info("handler exited");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// TODO: save persistant state..
}
private void feedMachine(StateMachine<String, String> stateMachine, String user, GenericMessage<String> event)
throws Exception {
stateMachine.sendEvent(event);
System.out.println("persist machine --- > state :" + stateMachine.getState().toString());
redisStateMachinePersister.persist(stateMachine, "testprefixSw:" + user);
}
private StateMachine<String, String> resetStateMachineFromStore(StateMachine<String, String> stateMachine,
String user) throws Exception {
StateMachine<String, String> machine = redisStateMachinePersister.restore(stateMachine, "testprefixSw:" + user);
System.out.println("restore machine --- > state :" + machine.getState().toString());
return machine;
}
Actions
#Bean
public Action<String, String> sendBasicTemplate() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
// MP: variables are the right way to do
String imeiNo = (String) context.getExtendedState().getVariables().get("imei");
String template = (String) context.getMessageHeader("template");
log.info("sending basic template " + template + " to " + imeiNo);
findTemplateNSend(context, template, imeiNo);
xbossBalanceCheck(context, imeiNo, "Direct Query");
setRiskyState(context, "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo, 0);
}
};
}
#Bean
public Action<String, String> processBasicTemplateReply() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
log.info("Result for basic template processing started");
log.info(context.getStateMachine().getState().getIds().toString());
String imeiNo = (String) context.getExtendedState().getVariables().get("imei");
saveDirectValues(context, imeiNo);
String fromState = (String) context.getExtendedState().getVariables().get("fromState");
String eventName = (String) context.getExtendedState().getVariables().get("eventName");
long trId = (Long) context.getMessageHeader("processId") != null? (Long) context.getMessageHeader("processId") : 0;
String key = "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo;
log.info("*Going to delete if exists key ==>" + key);
if (clearRiskyStateIfSet(context, key)) {
log.info("------------------------------Jedis Exists");
sendSubscriberEventLog(imeiNo, fromState, context.getStateMachine().getState().getId(), trId, eventName, false, "Query Event Success");
}
// mark as success sent
context.getStateMachine().sendEvent("SEQUENCE_COMPLETE");
}
};
}
#Bean
public Action<String, String> timeoutLogAction() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
// log.info("timeout log Action");
String imeiNo = (String) context.getStateMachine().getExtendedState().getVariables().get("imei");
// String imeiNo = (String)
// context.getExtendedState().getVariables().get("imei");
String fromState = (String) context.getExtendedState().getVariables().get("fromState");
String eventName = (String) context.getExtendedState().getVariables().get("eventName");
long trId = (Long) context.getMessageHeader("processId") != null ? (Long) context.getMessageHeader("processId") : 0;
String key = "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo;
log.info("*Going to delete if exists key ==>" + key);
if (clearRiskyStateIfSet(context, key)) {
log.info("------------------------------Jedis Exists at timeout. Event Failed");
sendSubscriberEventLog(imeiNo, fromState, context.getStateMachine().getId(), trId, eventName, true, "Direct Query Failed due to Timeout");
sendAlert(imeiNo, EventPriority.NORMAL, "Direct Query Failed due to Timeout");
}
}
};
}
So based on the above, Is there anything I'm missing so that the created state machines are not collected by garbage? or any other explanation as to why memory is being consumed with each request and it never gets released?
#Override
public Long createPost(Request request) {
Base.open();
ObjectMapper mapper = new ObjectMapper();
try {
Post newPost = mapper.readValue(request.body(), Post.class);
// Map values = ... initialize map
// newPost.saveIt();
} catch (IOException ex) {
Logger.getLogger(PostServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
}
Base.close();
return 1L;
}
From the official docs, this Map values = ... initialize map is not clear. I can do newPost.set("first_name", "Sam") but is there a better way instead of setting values likes this?
I'm not familiar with Spark (I'm the author of ActiveWeb ), but you can use filters to open/close connections instead of polluting your service classes:
http://sparkjava.com/documentation.html#filters
Additionally, if you can convert your request parameters into a java.util.Map, you then do this:
Post post = new Post();
post.fromMap(parameters);
if(post.save()){
//display success
}else{
Errors errors = post.errors();
//display errors
}
This is an example from ActiveWeb, but will help you with Spark too:
https://github.com/javalite/activeweb-simple/blob/master/src/main/java/app/controllers/BooksController.java
If I'm understanding correctly, you are looking at how to take values from a POST request and then use ActiveJDBC to save those values. I'm quite new as well and we are in the beginning stages of our app, but we are using SparkJava with ActiveJDBC.
The example is actual code, I didn't have time to simplify it. But basically we created a POJO for the model class. We originally extended the org.javalite.activejdbc.Model but we needed to handle audit fields (create, update user/time) and help translate from JSON, so we extended this with a custom class called CecilModel. But CecilModel extends the Model class.
We have a controller that receives the request. The request comes in as JSON that matches the field names of our model class. In our custom CecilModel class we map the JSON to a Map which then we use Model.fromMap method to hyrdrate the fields and puts it into our custom model POJO. We don't need the getters or setters, it's more for convenience. We just need our JSON request to have the same names as in our model.
Below is our code but maybe you can peek through it to see how we are doing it.
Our table model pojo.
package com.brookdale.model;
import java.sql.Timestamp;
import org.javalite.activejdbc.Model;
import org.javalite.activejdbc.annotations.BelongsTo;
import org.javalite.activejdbc.annotations.BelongsToParents;
import org.javalite.activejdbc.annotations.IdGenerator;
import org.javalite.activejdbc.annotations.IdName;
import org.javalite.activejdbc.annotations.Table;
import com.brookdale.model.activejdbc.CecilModel;
// This class handles mapping of data from the database to objects
// and back, including custom selection queries.
#Table("RECURRINGITEMSCHEDULE")
#BelongsToParents({
#BelongsTo(foreignKeyName="itemID",parent=Item.class),
#BelongsTo(foreignKeyName="contactAgreementID",parent=ContactAgreement.class),
#BelongsTo(foreignKeyName="unitOfMeasureCode",parent=UnitOfMeasure.class)
})
#IdGenerator("SQ_RECURRINGITEMSCHEDULE.nextVal")
#IdName("recurringItemScheduleID")
public class RecurringItem extends CecilModel {
public Long getRecurringItemScheduleID() {
return getLong("recurringItemScheduleID");
}
public void setRecurringItemScheduleID(Long recurringItemScheduleID) {
set("recurringItemScheduleID",recurringItemScheduleID);
}
public Long getContactAgreementID() {
return getLong("contactAgreementID");
}
public void setContactAgreementID(Long contactAgreementID) {
set("contactAgreementID",contactAgreementID);
}
public Long getItemID() {
return getLong("itemID");
}
public void setItemID(Long itemID) {
set("itemID",itemID);
}
public Double getUnitChargeAmt() {
return getDouble("unitChargeAmt");
}
public void setUnitChargeAmt(Double unitChargeAmt) {
set("unitChargeAmt",unitChargeAmt);
}
public Integer getUnitQty() {
return getInteger("unitQty");
}
public void setUnitQty(Integer unitQty) {
set("unitQty",unitQty);
}
public String getUnitOfMeasureCode() {
return getString("unitOfMeasureCode");
}
public void setUnitOfMeasureCode(String unitOfMeasureCode) {
set("unitOfMeasureCode",unitOfMeasureCode);
}
public Timestamp getLastGeneratedPeriodEndDate() {
return getTimestamp("lastGeneratedPeriodEndDate");
}
public void setLastGeneratedPeriodEndDate(Timestamp lastGeneratedPeriodEndDate) {
set("lastGeneratedPeriodEndDate",lastGeneratedPeriodEndDate);
}
public Timestamp getEffEndDate() {
return getTimestamp("effEndDate");
}
public void setEffEndDate(Timestamp effEndDate) {
set("effEndDate",effEndDate);
}
public Timestamp getEffStartDate() {
return getTimestamp("effStartDate");
}
public void setEffStartDate(Timestamp effStartDate) {
set("effStartDate",effStartDate);
}
#Override
public void validate() {
validatePresenceOf("unitofmeasurecode","itemid","unitqty","effstartdate","unitChargeAmt","contactAgreementID");
validateNumericalityOf("itemid","unitQty","contactAgreementID");
// check to make sure this is an update operation
if (!this.isNew()) {
RecurringItem ridb = RecurringItem.findById(this.getId());
if (ridb.getLastGeneratedPeriodEndDate() != null) {
if (this.getItemID() != ridb.getItemID())
this.addError("itemid", "Item can not be updated once a charge has been created.");
if (!this.getEffStartDate().equals(ridb.getEffStartDate()))
this.addError("effstartdate", "Effective start date can not be updated once a charge has been created.");
if (this.getUnitChargeAmt() != ridb.getUnitChargeAmt())
this.addError("unitchargeamt", "Unit charge amount can not be updated after last generated period end date has been set.");
if (this.getUnitQty() != ridb.getUnitQty())
this.addError("unitqty", "Unit quantity can not be updated after last generated period end date has been set.");
if (!this.getUnitOfMeasureCode().equals(ridb.getUnitOfMeasureCode()))
this.addError("", "Unit of measure can not be updated after last generated period end date has been set.");
}
}
if (this.getEffEndDate() != null && this.getEffStartDate().compareTo(this.getEffEndDate()) >= 0) {
this.addError("effenddate", "Effective end date can not come before the start date.");
}
}
}
Extends our custom Model class. This will extend the actual ActiveJDBC Model class.
package com.brookdale.model.activejdbc;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.javalite.activejdbc.Model;
import org.javalite.activejdbc.validation.ValidationBuilder;
import org.javalite.activejdbc.validation.ValidatorAdapter;
import com.brookdale.core.CLArgs;
import com.brookdale.security.bo.User;
public abstract class CecilModel extends Model {
private static final transient TypeReference<HashMap<String, Object>> mapType = new TypeReference<HashMap<String, Object>>() {};
private static final transient TypeReference<LinkedList<HashMap<String, Object>>> listMapType = new TypeReference<LinkedList<HashMap<String, Object>>>() {};
private static final transient SimpleDateFormat jsonDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
public Timestamp getUpdateDateTime() {
return getTimestamp("updateDateTime");
}
public void setUpdateDateTime(LocalDateTime updateDateTime) {
set("updateDateTime",updateDateTime == null ? null : Timestamp.valueOf(updateDateTime));
}
public Timestamp getCreateDateTime() {
return getTimestamp("createDateTime");
}
public void setCreateDateTime(LocalDateTime createDateTime) {
set("createDateTime",createDateTime == null ? null : Timestamp.valueOf(createDateTime));
}
public String getUpdateUsername() {
return getString("updateUsername");
}
public void setUpdateUsername(String updateUsername) {
set("updateUsername",updateUsername);
}
public String getCreateUsername() {
return getString("createUsername");
}
public void setCreateUsername(String createUsername) {
set("createUsername",createUsername);
}
public Long getUpdateTimeId() {
return getLong("updateTimeId");
}
public void setUpdateTimeId(Long updateTimeId) {
set("updateTimeId",updateTimeId);
}
public boolean save(User user) {
String userId = (CLArgs.args.isAuthenabled()) ? user.getUserid() : "TEST_MODE";
// insert
java.sql.Timestamp now = java.sql.Timestamp.valueOf(java.time.LocalDateTime.now());
if (this.getId() == null || this.getId().toString().equals("0")) {
this.setId(null);
this.set("createDateTime", now);
this.set("createUsername", userId);
this.set("updateDateTime", now);
this.set("updateUsername", userId);
this.set("updateTimeId", 1);
}
// update
else {
Long updatetimeid = this.getLong("updateTimeid");
this.set("updateDateTime", now);
this.set("updateUsername", userId);
this.set("updateTimeId", updatetimeid == null ? 1 : updatetimeid + 1);
}
return super.save();
}
public boolean saveIt(User user) {
String userId = (CLArgs.args.isAuthenabled()) ? user.getUserid() : "TEST_MODE";
// insert
java.sql.Timestamp now = java.sql.Timestamp.valueOf(java.time.LocalDateTime.now());
if (this.isNew()) {
this.setId(null);
this.set("createDateTime", now);
this.set("createUsername", userId);
this.set("updateDateTime", now);
this.set("updateUsername", userId);
this.set("updateTimeId", 1);
}
// update
else {
Long updatetimeid = this.getLong("updateTimeid");
this.set("updateDateTime", now);
this.set("updateUsername", userId);
this.set("updateTimeId", updatetimeid == null ? 1 : updatetimeid + 1);
}
return super.saveIt();
}
public boolean saveModel(User user, boolean insert) {
if(insert){
this.insertIt(user);
}else{
this.updateIt(user);
}
return super.saveIt();
}
public boolean insertIt(User user) {
// insert
java.sql.Timestamp now = java.sql.Timestamp.valueOf(java.time.LocalDateTime.now());
this.setId(null);
this.set("createDateTime", now);
this.set("createUsername", user.getUserid());
this.set("updateDateTime", now);
this.set("updateUsername", user.getUserid());
this.set("updateTimeId", 1);
return super.saveIt();
}
public boolean updateIt(User user) {
// insert
java.sql.Timestamp now = java.sql.Timestamp.valueOf(java.time.LocalDateTime.now());
Long updatetimeid = this.getLong("updateTimeid");
this.set("updateDateTime", now);
this.set("updateUsername", user.getUserid());
this.set("updateTimeId", updatetimeid == null ? 1 : updatetimeid + 1);
return super.saveIt();
}
// Convert a single ActiveJdbc Object to a json string
#SuppressWarnings("unchecked")
public String toJson() {
Map<String, Object> objMap = this.toMap();
try {
if (objMap.containsKey("parents")) {
Map<String, ?> parentsMap = (Map<String, ?>) objMap.get("parents");
for (String key: parentsMap.keySet()) {
objMap.put(key, parentsMap.get(key));
}
objMap.remove("parents");
}
if (objMap.containsKey("children")) {
Map<String, ?> childrenMap = (Map<String, ?>) objMap.get("children");
for (String key: childrenMap.keySet()) {
objMap.put(key, childrenMap.get(key));
}
objMap.remove("children");
}
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(jsonDateFormat);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(objMap);
} catch (Exception e) { throw new RuntimeException(e); }
}
// Converts a single json object into an ActiveJdbc Model
public <T extends CecilModel> T toObj(String json) {
try {
Map<String, Object> objMap = toMap(json);
convertDatesToTimestamp(objMap);
return this.fromMap(objMap);
} catch (Exception e) { throw new RuntimeException(e); }
}
// STATIC CONVERTERS FOR COLLECTIONS OF OBJECTS
// Convert an ActiveJdbc LazyList Collection to a JSON string
public static <T extends Model> String toJson(Collection<T> objCollection) {
//objCollection.load();
StringBuffer json = new StringBuffer("[ ");
for (T obj: objCollection) {
if (CecilModel.class.isInstance(obj)) {
json.append(((CecilModel)obj).toJson() + ",");
} else {
try {
json.append(new ObjectMapper().writeValueAsString(obj));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return json.substring(0, json.length()-1) + "]";
}
// Converts an array of json objects into a list of ActiveJdbc Models
public static <T extends Model> List<T> toObjList(String json, Class<T> cls) {
List<T> results = new LinkedList<T>();
try {
List<Map<String, Object>> objMapList = toMaps(json);
for (Map<String, Object> objMap: objMapList) {
convertDatesToTimestamp(objMap);
T obj = cls.newInstance().fromMap(objMap);
results.add(obj);
}
} catch (Exception e) { throw new RuntimeException(e); }
return results;
}
// Converts a single json object into a map of key:value pairs
private static Map<String, Object> toMap(String json) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(json, mapType);
} catch (IOException e) { throw new RuntimeException(e); }
}
// Converts an array of json objects into a list of maps
private static List<Map<String, Object>> toMaps(String json) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(json, listMapType);
} catch (IOException e) { throw new RuntimeException(e); }
}
// checks for Strings that are formatted in 'yyyy-MM-ddTHH:mm:ss.SSSZ' format
// if found it will replace the String value with a java.sql.Timestamp value in the objMap
private static final Map<String,Object> convertDatesToTimestamp(Map<String, Object> objMap) {
// attempt to convert all dates to java.sql.Timestamp
for (String key: objMap.keySet()) {
Object value = objMap.get(key);
System.out.println("Checking if '" + key + "=" + (value == null ? "null" : value.toString()) +"' is a date...");
if (value instanceof String && ((String) value).matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$")) {
String valuestr = (String) value;
System.out.println("DATE FOUND FOR '" + key + "' " + value);
objMap.put(key, Timestamp.valueOf(ZonedDateTime.parse(valuestr).toLocalDateTime()));
}
}
return objMap;
}
public static ValidationBuilder<?> validateAbsenceOf(String ... attributes) {
return validateWith(new ValidatorAdapter() {
#Override
public void validate(Model m) {
boolean containsAttribute = false;
for(String attribute:attributes) {
if(m.attributeNames().contains(attribute)) {
//model contains attribute, invalidate now !
m.addValidator(this, attribute);
break;
}
}
}
});
}
}
Our Controller
package com.brookdale.controller;
import static spark.Spark.get;
import static spark.Spark.post;
import static spark.Spark.put;
import org.codehaus.jackson.map.ObjectMapper;
import com.brookdale.model.RecurringItem;
import com.brookdale.model.activejdbc.CecilModel;
import com.brookdale.security.bo.User;
import com.brookdale.service.RecurringItemService;
public class RecurringItemController {
public RecurringItemController(final RecurringItemService service) {
// get list of recurring items based on the agreementid
get("/api/recurring/list/:agreementid", (req,res)-> {
String all = req.queryParams("all");
Long agreementid = Long.parseLong(req.params(":agreementid"));
//return RecurringItemAPI.searchByAgreement(Long.parseLong(req.params(":agreementid"))).toJson(true);
return CecilModel.toJson(service.findByAgreementId(agreementid, all != null));
});
// insert
post("/api/recurring/save", (req,res)-> {
//RecurringItem ri = ActiveJdbcJson.toObj(req.body(), RecurringItem.class);
RecurringItem ri = new RecurringItem().toObj(req.body());
// set unitqty to '1' since the field is not nullable, but not used
if (ri.getUnitQty() == null)
ri.setUnitQty(1);
System.out.println("ri to insert: " + new ObjectMapper().writeValueAsString(ri));
return service.saveRecurringItem(ri, (User) req.attribute("user")).toJson();
});
// update
put("/api/recurring/save", (req,res)-> {
//RecurringItem ri = ActiveJdbcJson.toObj(req.body(), RecurringItem.class);
RecurringItem ri = new RecurringItem().toObj(req.body());
System.out.println("ri to update: " + new ObjectMapper().writeValueAsString(ri));
return service.saveRecurringItem(ri, (User) req.attribute("user")).toJson();
});
}
}
Which calls our service layer to do the save.
package com.brookdale.service;
import org.javalite.activejdbc.LazyList;
import org.javalite.activejdbc.Model;
import com.brookdale.model.RecurringItem;
import com.brookdale.security.bo.User;
public class RecurringItemService {
public RecurringItemService() { }
public LazyList<Model> findByAgreementId(Long agreementId, boolean includeAll) {
if (includeAll)
return RecurringItem.find("contactAgreementID = ?", agreementId).orderBy("effstartdate desc");
else
return RecurringItem.find("contactAgreementID = ? and effenddate is null", agreementId).orderBy("effstartdate desc");
}
public RecurringItem saveRecurringItem(RecurringItem ri, User user) {
ri.saveIt(user);
return ri;
}
}