How to enter and test catch clause. Spring-boot, Junit, Mockito - spring

I'm trying to test the following code part. Especially the catch:
try {
preAuthCompleteResponse = preAuthCompleteClient
.getPreAuthCompleteResponse(preAuthCompleteServiceImpl.getPreAuthComplete(
preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest(preAuthCompleteModel)));
} catch (IOException | MarshalSendAndReceiveException e) {
logger.error(e.getMessage(), e);
return new ResponseEntity<>(
new SvsTransactionResponseModel(HttpStatus.BAD_REQUEST, e.getMessage()),
HttpStatus.BAD_REQUEST);
}
In one test I want expect that a IOException is catched and in the other test I want expect that a MarshalSendAndReceiveException is catched. Both return then the SvsTransactionResponseModel with status HttpStatus.BAD_REQUEST.
The preAuthCompleteClient.getPreAuthCompleteResponse method throws a MarshalSendAndReceiveException and preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest throws an IOException.
My test looks like that:
#RunWith(MockitoJUnitRunner.class)
public class PreAuthCompleteControllerTest {
#Mock
private PreAuthCompleteService preAuthCompleteServiceImpl;
#Mock
private PreAuthCompleteRequestService preAuthCompleteRequestServiceImpl;
#Mock
private PreAuthCompleteClient preAuthCompleteClient;
#Mock
private Errors errors;
private PreAuthCompleteController preAuthCompleteController;
#Before
public void setUp() {
preAuthCompleteController = new PreAuthCompleteController(preAuthCompleteServiceImpl,
preAuthCompleteRequestServiceImpl, preAuthCompleteClient);
}
#Test
public void testGetPreAuthCompleteExpectIOException() throws MarshalSendAndReceiveException, IOException {
when(preAuthCompleteClient.getPreAuthCompleteResponse(preAuthCompleteServiceImpl.getPreAuthComplete(
preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest(new PreAuthCompleteModel()))))
.thenReturn(new PreAuthCompleteResponse());
ResponseEntity<SvsTransactionResponseModel> responseEntity = (ResponseEntity<SvsTransactionResponseModel>) preAuthCompleteController
.getPreAuthComplete(null, errors);
assertTrue(responseEntity.getBody() != null);
assertTrue(responseEntity.getStatusCodeValue() == 400);
}
}
I tried different solution using Mockito.when, Mockito.thenThrow or doThrow and so on. But got different exceptions or unsuccesful tests. I am out of ideas.
How can I test that the exceptions are catched and the ResponseEntity return correctly.
P.S. All the mock config work.

To help make sense of things I have broken this call ...
preAuthCompleteResponse = preAuthCompleteClient
.getPreAuthCompleteResponse(preAuthCompleteServiceImpl.getPreAuthComplete(
preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest(preAuthCompleteModel)));
... down into its constituent parts:
PreAuthCompleteRequest preAuthCompleteRequest = preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest(preAuthCompleteModel);
PreAuthComplete preAuthComplete = preAuthCompleteServiceImpl.getPreAuthComplete(preAuthCompleteRequest);
PreAuthCompleteResponse preAuthCompleteResponse = preAuthCompleteClient.getPreAuthCompleteResponse(preAuthComplete);
I'm guessing about the return types (PreAuthCompleteRequest, PreAuthComplete etc) but you get the idea (I think :). Given the above call sequence, the following tests should pass:
#Test
public void badRequestWhenPreAuthCompleteResponseFails() {
// preAuthCompleteController.getPreAuthComplete() invokes
// preAuthCompleteClient.getPreAuthCompleteResponse()
// and any MarshalSendAndReceiveException throw by that client should result in a BAD_REQUEST
MarshalSendAndReceiveException expectedException = new MarshalSendAndReceiveException("boom!");
when(preAuthCompleteClient.getPreAuthCompleteResponse(Mockito.any(PreAuthComplete.class))).thenThrow(expectedException);
ResponseEntity<SvsTransactionResponseModel> responseEntity = (ResponseEntity<SvsTransactionResponseModel>) preAuthCompleteController
.getPreAuthComplete(null, errors);
assertTrue(responseEntity.getBody() != null);
assertTrue(responseEntity.getStatusCodeValue() == 400);
}
#Test
public void badRequestWhenPreAuthCompleteRequestFails() {
// preAuthCompleteController.getPreAuthComplete() invokes
// preAuthCompleteClient.getPreAuthCompleteResponse() which then invokes
// preAuthCompleteServiceImpl.getPreAuthComplete() which then invokes
// preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest
// and any IOException throw by that call should result in a BAD_REQUEST
IOException expectedException = new IOException("boom!");
when(preAuthCompleteRequestServiceImpl.getPreAuthCompleteRequest(Mockito.any(PreAuthCompleteModel.class))).thenThrow(expectedException);
ResponseEntity<SvsTransactionResponseModel> responseEntity = (ResponseEntity<SvsTransactionResponseModel>) preAuthCompleteController
.getPreAuthComplete(null, errors);
assertTrue(responseEntity.getBody() != null);
assertTrue(responseEntity.getStatusCodeValue() == 400);
}

Related

Groovy Spock: How do you pass in new Exception into when method without throwing error right away

I am trying to test a kafka error handler that takes in an Exception but as soon as I declare it in spock it actually throws it.
def 'test example'() {
when:
service.emitError(new Exception('test exception'))
then:
// do some tests
}
I have tried declaring it in a wrapped java class and running that in main will NOT throw an error but if I pull it into spock it will process it incorrectly.
I am trying to see if I am doing it wrong or if I can't test this with spock.
With help from Jeff I realized that it was an error on mock kafka template. When you have to pass an exception into a mock (not sure if it is just KafkaTemplate specific) and the expected mock fails something bubbles up and my try catch caught that instead. I recognize I should have posted the original code - and will in the future. This is testing on pre-refactored code that didn't have tests (not TTD)
I was missing .key('key') which was failing it.
Emitter
public class KafkaErrorNotificationEmitter {
private final KafkaTemplate<String, TopicMessageData> kafkaTemplate;
private final ObjectMapper objectMapper;
private final TemporalConfig.TimeKeeper timeKeeper;
private final String internalErrorTopic;
public KafkaErrorNotificationEmitter(
KafkaTemplate<String, TopicMessageData> kafkaTemplate,
ObjectMapper objectMapper,
TemporalConfig.TimeKeeper timeKeeper,
#Value("${kafka.error.topic}") String internalErrorTopic
) {
this.kafkaTemplate = kafkaTemplate;
this.objectMapper = objectMapper;
this.timeKeeper = timeKeeper;
this.internalErrorTopic = internalErrorTopic;
}
public void emitError(#Nullable KesMessageProperties kesMessageProperties, Exception ex) {
assert kesMessageProperties != null;
String entityName = kesMessageProperties.getEntityName();
log.warn("Failed message ({}). Sending to KES.", entityName, ex);
String key = kesMessageProperties.getMessage().getKey();
try {
TopicMessageData errorMessage =
TopicMessageData
.builder()
.sourceTopic(kesMessageProperties.getTopic())
.exceptionMessage(ex.getMessage())
.key(key)
.listenerType(kesMessageProperties.getListenerType())
.occurrenceTime(timeKeeper.nowZdt())
.payload(objectMapper.writeValueAsString(kesMessageProperties.getMessage()))
.build();
sendEmitError(errorMessage);
} catch (Exception e) {
log.error("Failed to send error ({}) notification for {}", entityName, key, e);
}
}
private void sendEmitError(final TopicMessageData topicMessageData) {
log.debug("Sending error message for: {}", topicMessageData);
kafkaTemplate.send(internalErrorTopic, topicMessageData);
}
}
Test
class KafkaErrorNotificationEmitterSpec extends Specification {
KafkaTemplate<String, TopicMessageData> kafkaTemplate = Mock()
ObjectMapper objectMapper = new ObjectMapper()
TemporalConfig.TimeKeeper timeKeeper = Mock()
String internalErrorTopic = 'kes.error.test'
def kafkaErrorNotificationEmitter = new KafkaErrorNotificationEmitter(
kafkaTemplate,
objectMapper,
timeKeeper,
internalErrorTopic
)
#Shared
def errorMessage = 'Test exception'
#Shared
Exception exception = new Exception(errorMessage)
def 'emitError throws uncaught NPE'() {
setup:
def properties = new KesMessageProperties(message: null)
when:
kafkaErrorNotificationEmitter.emitError(properties, exception)
then:
0 * _
thrown NullPointerException
}
def 'emitError throws caught exception'() {
setup:
def properties = new KesMessageProperties(
message: new IKafkaMessage() {
#Override
String getKey() {
return null
}
}
)
when:
kafkaErrorNotificationEmitter.emitError(properties, exception)
then:
1 * timeKeeper.nowZdt() >> { throw new RuntimeException() }
0 * _
}
def 'emitError success'() {
setup:
def listenerType = 'test-error'
def properties = new KesMessageProperties(
listenerType: listenerType,
message: new IKafkaMessage() {
#Override
String getKey() {
return 'key'
}
}
)
def now = ZonedDateTime.now()
def errorData =
TopicMessageData
.builder()
.exceptionMessage(errorMessage)
.listenerType(listenerType)
//.key('key') // this is what was missing!!!
.occurrenceTime(now)
.payload('{\"key\":\"key\"}')
.build()
when:
kafkaErrorNotificationEmitter.emitError(properties, exception)
then:
1 * timeKeeper.nowZdt() >> now
1 * kafkaTemplate.send(internalErrorTopic, errorData) >> Mock(ListenableFuture)
}
}

JUnit4 with Mockito for unit testing

public class DgiQtyAction extends DispatchAction {
private final Logger mLog = Logger.getLogger(this.getClass());
public ActionForward fnDgiQty(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
mLog.debug(request.getParameter(EcoConstants.ecopidid));
ActionErrors errorMessage = new ActionErrors();
if(request.getSession().getAttribute(EcoConstants.userBean)==null)
{
request.setAttribute(EcoConstants.ERROR_MESSAGE,EcoConstants.SESSION_TIMEOUT);
errorMessage.add(Globals.MESSAGE_KEY, new ActionMessage(EcoConstants.error_message,EcoConstants.SESSION_TIMEOUT));
saveMessages(request, errorMessage);
request.setAttribute(EcoConstants.errorMessageType,EcoConstants.errorMessageType);
return mapping.findForward(EcoConstants.SESSION_FORWARD);
}
String ecoPidID = (String) request.getParameter(EcoConstants.ecopidid);
String pidId = ESAPI.encoder().encodeForHTML((String) request.getParameter(EcoConstants.pidid));
mLog.debug("pidid --------" + pidId);
mLog.debug("ecopidpid --------" + ecoPidID);
ArrayList dgiQty = new ArrayList();
NeedDgiForm niForm = new NeedDgiForm();
try {
NeedDgiBD niBD = new NeedDgiBD();
if (ecoPidID != null) {
dgiQty = niBD.getDgiQty(ecoPidID);
if (dgiQty.size() != 0) {
mLog.debug(dgiQty.get(0).toString());
if (dgiQty.get(0).toString().equals(EcoConstants.hundred)) {
niForm.setGlqtyList(new ArrayList());
request.setAttribute(EcoConstants.pidId, pidId);
return mapping.findForward(EcoConstants.SuccessInfo);
} else {
mLog.debug("fnBug 1----------------> " + dgiQty.get(0));
mLog.info("dgiQty sizeeeee: :" + dgiQty.size());
niForm.setGlqtyList(dgiQty);
}
}
}
} catch (Exception e) {
//log.error("General Exception in DgiQtyAction.fnDgiQty: "
// + e.getMessage(), e);
request.setAttribute(EcoConstants.ERROR_MESSAGE, e.getMessage());
return mapping.findForward(EcoConstants.ERROR_PAGE);
}
mLog.debug("pidid after wards--------" + pidId);
request.setAttribute(EcoConstants.pidId, pidId);
request.setAttribute(mapping.getAttribute(), niForm);
return mapping.findForward(EcoConstants.SuccessInfo);
}
}
public class DgiQtyActionTest {
ActionMapping am;
ActionForm af;
DgiQtyAction dat;
private MockHttpSession mocksession;
private MockHttpServletRequest mockrequest;
private MockHttpServletResponse mockresponse;
#Test
public void fnDgiQty() throws Exception
{
mocksession = new MockHttpSession();
mockrequest = new MockHttpServletRequest();
mockresponse = new MockHttpServletResponse();
((MockHttpServletRequest) mockrequest).setSession(mocksession);
mocksession.setAttribute("userBean","userBean");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockrequest));
mockrequest.addParameter("ecopid","something");
mockrequest.addParameter("pid","<script>");
Encoder instance = ESAPI.encoder();
assertEquals("something",mockrequest.getParameter("ecopid"));
assertEquals("<script>",instance.encodeForHTML(mockrequest.getParameter("pid")));
dat=mock(DgiQtyAction.class);
am=mock(ActionMapping.class);
af=mock(ActionForm.class);
dat.fnDgiQty(am,af,mockrequest, mockresponse);
}
}
I wrote the unit test case for above class. i ran this code through jenkins and used sonarqube as code coverage.I need to cover the ESAPi encoder for the parameter, it got build success but the coverage percentage doesn't increase. i couldn't found the mistake in it. pls help me guys. Thanks in Advance

Spring-Boot | Test Service Containing Call to #Async Method

Want to Unit Test a Service that contains call to a #Aysnc method which return CompletableFuture Object.But the future object is always null (during testing) causing NullPointerException.
future.get() causes the error
Test Code
#RunWith(MockitoJUnitRunner.class)
public class ContainerValidatorTest {
#Mock
QueryGenerator queryGenerator;
#Mock
SplunkService splunkService;
#InjectMocks
private ContainerValidatorImpl containerValidatorImpl;
#Test
public void validateContainerTestWithNullData(){
CacheItemId cacheItemId = null;
String container = null;
assertFalse(containerValidatorImpl.validateContainer(cacheItemId,container));
}
}
Service Code
#Override
public boolean validateContainer(CacheItemId cacheItemId, String container) {
Query query = queryGenerator.getUserDetailsFromCacheInfoQuery(cacheItemId);
String response;
try {
CompletableFuture<String> future = splunkService.doExecuteQuery(query);
response = future.get();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error While Fetching User Details : "+ e.getLocalizedMessage());
}
System.out.println(response);
JsonArray jsonArray = new JsonParser().parse(response).getAsJsonArray();
if(!jsonArray.isJsonNull()) {
return jsonArray.get(0).getAsJsonObject().get("TAG").getAsString().equalsIgnoreCase(container);
}
throw new RuntimeException("Not Able to Find UserDetails");
}
You haven’t set any expectations on splunkService mock.
Then you call doExecuteQuery on the mock instance
With no expectations for the method call, Mockito returns default value for the method return type (null for Objects)
To fix, record your expectations with when and thenReturn
Update
#Test
public void validateContainerTestWithNullData(){
CacheItemId cacheItemId = null;
String container = null;
when(splunkService.doExecuteQuery(any())).thenReturn(CompletableFuture.completedFuture("completedVal"));
assertFalse(containerValidatorImpl.validateContainer(cacheItemId,container));
}

transactional unit testing with ObjectifyService - no rollback happening

We are trying to use google cloud datastore in our project and trying to use objectify as the ORM since google recommends it. I have carefully used and tried everything i could read about and think of but somehow the transactions don't seem to work. Following is my code and setup.
#RunWith(SpringRunner.class)
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ContextConfiguration(classes = { CoreTestConfiguration.class })
public class TestObjectifyTransactionAspect {
private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
// Our tests assume strong consistency
new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy(),
new LocalMemcacheServiceTestConfig(), new LocalTaskQueueTestConfig());
private Closeable closeableSession;
#Autowired
private DummyService dummyService;
#BeforeClass
public static void setUpBeforeClass() {
// Reset the Factory so that all translators work properly.
ObjectifyService.setFactory(new ObjectifyFactory());
}
/**
* #throws java.lang.Exception
*/
#Before
public void setUp() throws Exception {
System.setProperty("DATASTORE_EMULATOR_HOST", "localhost:8081");
ObjectifyService.register(UserEntity.class);
this.closeableSession = ObjectifyService.begin();
this.helper.setUp();
}
/**
* #throws java.lang.Exception
*/
#After
public void tearDown() throws Exception {
AsyncCacheFilter.complete();
this.closeableSession.close();
this.helper.tearDown();
}
#Test
public void testTransactionMutationRollback() {
// save initial list of users
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
ObjectifyService.ofy().save().entities(users).now();
try {
dummyService.mutateDataWithException("username_1", 6L);
} catch (Exception e) {
e.printStackTrace();
}
List<UserEntity> users2 = this.dummyService.findAllUsers();
Assert.assertEquals("Size mismatch on rollback", users2.size(), 10);
boolean foundUserIdSix = false;
for (UserEntity userEntity : users2) {
if (userEntity.getUserId() == 1) {
Assert.assertEquals("Username update failed in transactional context rollback.", "username_1",
userEntity.getUsername());
}
if (userEntity.getUserId() == 6) {
foundUserIdSix = true;
}
}
if (!foundUserIdSix) {
Assert.fail("Deleted user with userId 6 but it is not rolledback.");
}
}
}
Since I am using spring, idea is to use an aspect with a custom annotation to weave objectify.transact around the spring service beans methods that are calling my daos.
But somehow the update due to ObjectifyService.ofy().save().entities(users).now(); is not gettign rollbacked though the exception throws causes Objectify to run its rollback code. I tried printing the ObjectifyImpl instance hashcodes and they are all same but still its not rollbacking.
Can someone help me understand what am i doing wrong? Havent tried the actual web based setup yet...if it cant pass transnational test cases there is no point in actual transaction usage in a web request scenario.
Update: Adding aspect, services, dao as well to make a complete picture. The code uses spring boot.
DAO class. Note i am not using any transactions here because as per code of com.googlecode.objectify.impl.TransactorNo.transactOnce(ObjectifyImpl<O>, Work<R>) a transnational ObjectifyImpl is flushed and committed in this method which i don't want. I want commit to happen once and rest all to join in on that transaction. Basically this is the wrong code in com.googlecode.objectify.impl.TransactorNo ..... i will try to explain my understanding a later in the question.
#Component
public class DummyDaoImpl implements DummyDao {
#Override
public List<UserEntity> loadAll() {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
return query.list();
}
#Override
public List<UserEntity> findByUserId(Long userId) {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
//query = query.filterKey(Key.create(UserEntity.class, userId));
return query.list();
}
#Override
public List<UserEntity> findByUsername(String username) {
return ObjectifyService.ofy().transactionless().load().type(UserEntity.class).filter("username", username).list();
}
#Override
public void update(UserEntity userEntity) {
ObjectifyService.ofy().save().entity(userEntity);
}
#Override
public void update(Iterable<UserEntity> userEntities) {
ObjectifyService.ofy().save().entities(userEntities);
}
#Override
public void delete(Long userId) {
ObjectifyService.ofy().delete().key(Key.create(UserEntity.class, userId));
}
}
Below is the Service class
#Service
public class DummyServiceImpl implements DummyService {
private static final Logger LOGGER = LoggerFactory.getLogger(DummyServiceImpl.class);
#Autowired
private DummyDao dummyDao;
public void saveDummydata() {
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
this.dummyDao.update(users);
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#mutateDataWithException(java.lang.String, java.lang.Long)
*/
#Override
#ObjectifyTransactional
public void mutateDataWithException(String usernameToMutate, Long userIdToDelete) throws Exception {
//update one
LOGGER.info("Attempting to update UserEntity with username={}", "username_1");
List<UserEntity> mutatedUsersList = new ArrayList<UserEntity>();
List<UserEntity> users = dummyDao.findByUsername(usernameToMutate);
for (UserEntity userEntity : users) {
userEntity.setUsername(userEntity.getUsername() + "_updated");
mutatedUsersList.add(userEntity);
}
dummyDao.update(mutatedUsersList);
//delete another
UserEntity user = dummyDao.findByUserId(userIdToDelete).get(0);
LOGGER.info("Attempting to delete UserEntity with userId={}", user.getUserId());
dummyDao.delete(user.getUserId());
throw new RuntimeException("Dummy Exception");
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#findAllUsers()
*/
#Override
public List<UserEntity> findAllUsers() {
return dummyDao.loadAll();
}
Aspect which wraps the method annoted with ObjectifyTransactional as a transact work.
#Aspect
#Component
public class ObjectifyTransactionAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectifyTransactionAspect.class);
#Around(value = "execution(* *(..)) && #annotation(objectifyTransactional)")
public Object objectifyTransactAdvise(final ProceedingJoinPoint pjp, ObjectifyTransactional objectifyTransactional) throws Throwable {
try {
Object result = null;
Work<Object> work = new Work<Object>() {
#Override
public Object run() {
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new ObjectifyTransactionExceptionWrapper(throwable);
}
}
};
switch (objectifyTransactional.propagation()) {
case REQUIRES_NEW:
int limitTries = objectifyTransactional.limitTries();
if(limitTries <= 0) {
Exception illegalStateException = new IllegalStateException("limitTries must be more than 0.");
throw new ObjectifyTransactionExceptionWrapper(illegalStateException);
} else {
if(limitTries == Integer.MAX_VALUE) {
result = ObjectifyService.ofy().transactNew(work);
} else {
result = ObjectifyService.ofy().transactNew(limitTries, work);
}
}
break;
case NOT_SUPPORTED :
case NEVER :
case MANDATORY :
result = ObjectifyService.ofy().execute(objectifyTransactional.propagation(), work);
break;
case REQUIRED :
case SUPPORTS :
ObjectifyService.ofy().transact(work);
break;
default:
break;
}
return result;
} catch (ObjectifyTransactionExceptionWrapper e) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
LOGGER.error("An exception occured while executing [{}.{}] in a transactional context."
, packageName, methodName, e);
throw e.getCause();
} catch (Throwable ex) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
String fullyQualifiedmethodName = packageName + "." + methodName;
throw new RuntimeException("Unexpected exception while executing ["
+ fullyQualifiedmethodName + "] in a transactional context.", ex);
}
}
}
Now the problem code part that i see is as follows in com.googlecode.objectify.impl.TransactorNo:
#Override
public <R> R transact(ObjectifyImpl<O> parent, Work<R> work) {
return this.transactNew(parent, Integer.MAX_VALUE, work);
}
#Override
public <R> R transactNew(ObjectifyImpl<O> parent, int limitTries, Work<R> work) {
Preconditions.checkArgument(limitTries >= 1);
while (true) {
try {
return transactOnce(parent, work);
} catch (ConcurrentModificationException ex) {
if (--limitTries > 0) {
if (log.isLoggable(Level.WARNING))
log.warning("Optimistic concurrency failure for " + work + " (retrying): " + ex);
if (log.isLoggable(Level.FINEST))
log.log(Level.FINEST, "Details of optimistic concurrency failure", ex);
} else {
throw ex;
}
}
}
}
private <R> R transactOnce(ObjectifyImpl<O> parent, Work<R> work) {
ObjectifyImpl<O> txnOfy = startTransaction(parent);
ObjectifyService.push(txnOfy);
boolean committedSuccessfully = false;
try {
R result = work.run();
txnOfy.flush();
txnOfy.getTransaction().commit();
committedSuccessfully = true;
return result;
}
finally
{
if (txnOfy.getTransaction().isActive()) {
try {
txnOfy.getTransaction().rollback();
} catch (RuntimeException ex) {
log.log(Level.SEVERE, "Rollback failed, suppressing error", ex);
}
}
ObjectifyService.pop();
if (committedSuccessfully) {
txnOfy.getTransaction().runCommitListeners();
}
}
}
transactOnce is by code / design always using a single transaction to do things. It will either commit or rollback the transaction. there is no provision to chain transactions like a normal enterprise app would want.... service -> calls multiple dao methods in a single transaction and commits or rollbacks depending on how things look.
keeping this in mind, i removed all annotations and transact method calls in my dao methods so that they don't start an explicit transaction and the aspect in service wraps the service method in transact and ultimately in transactOnce...so basically the service method is running in a transaction and no new transaction is getting fired again. This is a very basic scenario, in actual production apps services can call other service methods and they might have the annotation on them and we could still end up in a chained transaction..but anyway...that is a different problem to solve....
I know NoSQLs dont support write consistency at table or inter table levels so am I asking too much from google cloud datastore?

Freemarker removeIntrospectionInfo does not work with DCEVM after model hotswap

I am using Freemarker and DCEVM+HotSwapManager agent. This basically allows me to hotswap classes even when adding/removing methods.
Everything works like charm until Freemarker uses hotswapped class as model. It's throwing freemarker.ext.beans.InvalidPropertyException: No such bean property on me even though reflection shows that the method is there (checked during debug session).
I am using
final Method clearInfoMethod = beanWrapper.getClass().getDeclaredMethod("removeIntrospectionInfo", Class.class);
clearInfoMethod.setAccessible(true);
clearInfoMethod.invoke(clazz);
to clear the cache, but it does not work. I even tried to obtain classCache member field and clear it using reflection but it does not work too.
What am I doing wrong?
I just need to force freemarker to throw away any introspection on model class/classes he has already obtained.
Is there any way?
UPDATE
Example code
Application.java
// Application.java
public class Application
{
public static final String TEMPLATE_PATH = "TemplatePath";
public static final String DEFAULT_TEMPLATE_PATH = "./";
private static Application INSTANCE;
private Configuration freemarkerConfiguration;
private BeansWrapper beanWrapper;
public static void main(String[] args)
{
final Application application = new Application();
INSTANCE = application;
try
{
application.run(args);
}
catch (InterruptedException e)
{
System.out.println("Exiting");
}
catch (IOException e)
{
System.out.println("IO Error");
e.printStackTrace();
}
}
public Configuration getFreemarkerConfiguration()
{
return freemarkerConfiguration;
}
public static Application getInstance()
{
return INSTANCE;
}
private void run(String[] args) throws InterruptedException, IOException
{
final String templatePath = System.getProperty(TEMPLATE_PATH) != null
? System.getProperty(TEMPLATE_PATH)
: DEFAULT_TEMPLATE_PATH;
final Configuration configuration = new Configuration();
freemarkerConfiguration = configuration;
beanWrapper = new BeansWrapper();
beanWrapper.setUseCache(false);
configuration.setObjectWrapper(beanWrapper);
try
{
final File templateDir = new File(templatePath);
configuration.setTemplateLoader(new FileTemplateLoader(templateDir));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
final RunnerImpl runner = new RunnerImpl();
try
{
runner.run(args);
}
catch (RuntimeException e)
{
e.printStackTrace();
}
}
public BeansWrapper getBeanWrapper()
{
return beanWrapper;
}
}
RunnerImpl.java
// RunnerImpl.java
public class RunnerImpl implements Runner
{
#Override
public void run(String[] args) throws InterruptedException
{
long counter = 0;
while(true)
{
++counter;
System.out.printf("Run %d\n", counter);
// Application.getInstance().getFreemarkerConfiguration().setObjectWrapper(new BeansWrapper());
Application.getInstance().getBeanWrapper().clearClassIntrospecitonCache();
final Worker worker = new Worker();
worker.doWork();
Thread.sleep(1000);
}
}
Worker.java
// Worker.java
public class Worker
{
void doWork()
{
final Application application = Application.getInstance();
final Configuration freemarkerConfiguration = application.getFreemarkerConfiguration();
try
{
final Template template = freemarkerConfiguration.getTemplate("test.ftl");
final Model model = new Model();
final PrintWriter printWriter = new PrintWriter(System.out);
printObjectInto(model);
System.out.println("-----TEMPLATE MACRO PROCESSING-----");
template.process(model, printWriter);
System.out.println();
System.out.println("-----END OF PROCESSING------");
System.out.println();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (TemplateException e)
{
e.printStackTrace();
}
}
private void printObjectInto(Object o)
{
final Class<?> aClass = o.getClass();
final Method[] methods = aClass.getDeclaredMethods();
for (final Method method : methods)
{
System.out.println(String.format("Method name: %s, public: %s", method.getName(), Modifier.isPublic(method.getModifiers())));
}
}
}
Model.java
// Model.java
public class Model
{
public String getMessage()
{
return "Hello";
}
public String getAnotherMessage()
{
return "Hello World!";
}
}
This example does not work at all. Even changing BeansWrapper during runtime won't have any effect.
BeansWrapper (and DefaultObjectWrapper's, etc.) introspection cache relies on java.beans.Introspector.getBeanInfo(aClass), not on reflection. (That's because it treats objects as JavaBeans.) java.beans.Introspector has its own internal cache, so it can return stale information, and in that case BeansWrapper will just recreate its own class introspection data based on that stale information. As of java.beans.Introspector's caching, it's in fact correct, as it builds on the assumption that classes in Java are immutable. If something breaks that basic rule, it should ensure that java.beans.Introspector's cache is cleared (and many other caches...), or else it's not just FreeMarker that will break. At JRebel for example they made a lot of effort to clear all kind of caches. I guess DCEVM doesn't have the resources for that. So then, it seems you have to call Introspector.flushCaches() yourself.
Update: For a while (Java 7, maybe 6) java.beans.Introspector has one cache per thread group, so you have call flushCaches() from all thread groups. And this all is actually implementation detail that, in principle, can change any time. And sadly, the JavaDoc of Introspector.flushCaches() doesn't warn you...

Resources