Spring: How to Resolve a Property When There Are Multiple Resolvers? - spring

We are building several Microservices using the Spring Cloud framework. One of the services has dependencies on some legacy shared libraries, and imports various XML files for bean configuration. The problem we are facing is that through these imports, multiple property resolvers are brought in and thus the following code in AbstractBeanFactory is failing to resolve spring.application.name because the value comes in as ${spring.application.name:unknown} that the first resolver fails to resolve and thus sets result to unknown. embeddedValueResolver does have a resolver than can resolve the property but because the property is set to it's default by a previous resolver, it doesn't get a chance. This is causing the service registration with Eureka to fail with a NPE.
#Override
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}

Answering my own question, I fixed the issue using a BeanDefinitionRegistryPostProcessor. Related JIRA SPR-6428 had been filed by another user but was closed.
/**
* Removes {#link org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} classes that come before
* {#link PropertySourcesPlaceholderConfigurer} and fail to resolve Spring Cloud properties, thus setting them to default.
* One such property is {#code spring.application.name} that gets set to 'unknown' thus causing registration with
* discovery service to fail. This class collects the {#code locations} from these offending
* {#code PropertyPlaceholderConfigurer} and later adds to the end of property sources available from
* {#link org.springframework.core.env.Environment}.
* <p>
* c.f. https://jira.spring.io/browse/SPR-6428
*
* #author Abhijit Sarkar
*/
#Component
#Slf4j
public class PropertyPlaceholderConfigurerPostProcessor implements BeanDefinitionRegistryPostProcessor {
private final Set<String> locations = new HashSet<>();
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
List<String> propertyPlaceholderConfigurers = Arrays.stream(beanDefinitionNames)
.filter(name -> name.contains("PropertyPlaceholderConfigurer"))
.collect(toList());
for (String name : propertyPlaceholderConfigurers) {
BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(name);
TypedStringValue location = (TypedStringValue) beanDefinition.getPropertyValues().get("location");
if (location != null) {
String value = location.getValue();
log.info("Found location: {}.", location);
/* Remove 'classpath:' prefix, if present. It later creates problem with reading the file. */
locations.add(removeClasspathPrefixIfPresent(value));
log.info("Removing bean definition: {}.", name);
beanDefinitionRegistry.removeBeanDefinition(name);
}
}
}
private String removeClasspathPrefixIfPresent(String location) {
int classpathPrefixIdx = location.lastIndexOf(':');
return classpathPrefixIdx > 0 ? location.substring(++classpathPrefixIdx) : location;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
PropertySourcesPlaceholderConfigurer configurer =
beanFactory.getBean(PropertySourcesPlaceholderConfigurer.class);
MutablePropertySources propertySources = getPropertySources(configurer);
locations.stream()
.map(locationToPropertySrc)
.forEach(propertySources::addLast);
}
private MutablePropertySources getPropertySources(PropertySourcesPlaceholderConfigurer configurer) {
/* I don't like this but PropertySourcesPlaceholderConfigurer has no getter for environment. */
Field envField = null;
try {
envField = PropertySourcesPlaceholderConfigurer.class.getDeclaredField("environment");
envField.setAccessible(true);
ConfigurableEnvironment env = (ConfigurableEnvironment) envField.get(configurer);
return env.getPropertySources();
} catch (ReflectiveOperationException e) {
throw new ApplicationContextException("Our little hack didn't work. Failed to read field: environment.", e);
}
}
Function<String, PropertySource> locationToPropertySrc = location -> {
ClassPathResource resource = new ClassPathResource(location);
try {
Properties props = PropertiesLoaderUtils.loadProperties(resource);
String filename = getFilename(location);
log.debug("Adding property source with name: {} and location: {}.", filename, location);
return new PropertiesPropertySource(filename, props);
} catch (IOException e) {
throw new ApplicationContextException(
String.format("Failed to read from location: %s.", location), e);
}
};
private String getFilename(String location) {
return location.substring(location.lastIndexOf('/') + 1);
}
}

Related

Loading value from json upon start up application

I want to load the values from json file upon the Spring Boot Application is started.
My code for the Configuration File is like the below:
#Configuration
#Getter
public class FedexAPIConfig {
private final static String JSON_FILE = "/static/config/fedex-api-credentials.json";
private final boolean IS_PRODUCTION = false;
private FedexAPICred apiCredentials;
public FedexAPIConfig() {
try (InputStream in = getClass().getResourceAsStream(JSON_FILE);
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
JSONObject json = new JSONObject();
// this.apiCredentials = new JSONObject(new JSONTokener(reader));
if (IS_PRODUCTION) {
json = new JSONObject(new JSONTokener(reader)).getJSONObject("production");
} else {
json = new JSONObject(new JSONTokener(reader)).getJSONObject("test");
}
System.out.println(json.toString());
this.apiCredentials = FedexAPICred.builder()
.url(json.optString("url"))
.apiKey(json.optString("api_key"))
.secretKey(json.optString("secret_key"))
.build();
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
and with this, when the application is in progress of startup, values are successfully printed on the console.Startup console log
When I tried to call this value from other ordinary class, like the below:, it brings nothing but just throws NullPointerException... What are my faults and what shall I do?
public class FedexOAuthTokenManager extends OAuthToken {
private static final String VALIDATE_TOKEN_URL = "/oauth/token";
private static final String GRANT_TYPE_CLIENT = "client_credentials";
private static final String GRANT_TYPE_CSP = "csp_credentials";
#Autowired
private FedexAPIConfig fedexApiConfig;
#Autowired
private Token token;
#Override
public void validateToken() {
// This is the part where "fedexApiConfig" is null.
FedexAPICred fedexApiCred = fedexApiConfig.getApiCredentials();
Response response = null;
try {
RequestBody body = new FormBody.Builder()
.add("grant_type", GRANT_TYPE_CLIENT)
.add("client_id", fedexApiCred.getApiKey())
.add("client_secret", fedexApiCred.getSecretKey())
.build();
response = new HttpClient().post(fedexApiCred.getUrl() + VALIDATE_TOKEN_URL, body);
if (response.code() == 200) {
JSONObject json = new JSONObject(response.body().string());
token.setAccessToken(json.optString("access_token"));
token.setTokenType(json.optString("token_type"));
token.setExpiredIn(json.optInt("expires_in"));
token.setExpiredDateTime(LocalDateTime.now().plusSeconds(json.optInt("expires_in")));
token.setScope(json.optString("scope"));
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
fedexApiConfg is null even though I autowired it in prior to call.
And this FedexOAuthTokenManager is called from other #Component class by new FedexOAuthTokenManager()
Did you try like below?
Step 1: Create one Configuration class like below
public class DemoConfig implements ApplicationListener<ApplicationPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
//Load the values from the JSON file and populate the application
//properties dynamically
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put("spring.datasource.url", "<my value>");
//Add more properties
environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
To listen to a context event, a bean should implement the ApplicationListener interface which has just one method onApplicationEvent().The ApplicationPreparedEvent is invoked very early in the lifecycle of the application
Step 2: Customize in src/main/resources/META-INF/spring.factories
org.springframework.context.ApplicationListener=com.example.demo.DemoConfig
Step 3: #Value in spring boot is commonly used to inject the configuration values into the spring boot application. Access the properties as per your wish.
#Value("${spring.datasource.url}")
private String valueFromJSon;
Try this sample first in your local machine and then modify your changes accordingly.
Refer - https://www.baeldung.com/spring-value-annotation
Refer - https://www.knowledgefactory.net/2021/02/aws-secret-manager-service-as.html

Spring Boot custom annotation design not working

I am following a tutorial by PacktPublishing where some annotations are used in an example,
but the code is from 2018 and there have probably been some changes.
Spring does not recognize the Annotation when creating a bean.
Specifically, here is an annotation design that just does not work for me locally:
link
Some important code snippets are:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface ChannelHandler {
/**
* Channel patter, alias of value()
*/
String pattern() default "";
/**
* The channel pattern that the handler will be mapped to by {#link WebSocketRequestDispatcher}
* using Spring's {#link org.springframework.util.AntPathMatcher}
*/
String value() default "";
}
#ChannelHandler("/board/*")
public class BoardChannelHandler {
private static final Logger log = LoggerFactory.getLogger(BoardChannelHandler.class);
#Action("subscribe")
public void subscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Subscribe to channel `{}`", session.id(), channel);
SubscriptionHub.subscribe(session, channel);
}
#Action("unsubscribe")
public void unsubscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Unsubscribe from channel `{}`", session.id(), channel);
SubscriptionHub.unsubscribe(session, channel);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Action {
/**
* The action pattern. It needs to be an exact match.
* <p>For example, "subscribe"
*/
String value() default "";
}
Can you see what the issue is here? Is there some other annotation missing for newer versions
of Spring?
UPDATE - adding other necessary code.
public class ChannelHandlerInvoker {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerInvoker.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private String channelPattern;
private Object handler;
// Key is the action, value is the method to handle that action
private final Map<String, Method> actionMethods = new HashMap<>();
public ChannelHandlerInvoker(Object handler) {
Assert.notNull(handler, "Parameter `handler` must not be null");
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
Assert.notNull(handlerAnnotation, "Parameter `handler` must have annotation #ChannelHandler");
Method[] methods = handlerClass.getMethods();
for (Method method : methods) {
Action actionAnnotation = method.getAnnotation(Action.class);
if (actionAnnotation == null) {
continue;
}
String action = actionAnnotation.value();
actionMethods.put(action, method);
log.debug("Mapped action `{}` in channel handler `{}#{}`", action, handlerClass.getName(), method);
}
this.channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
this.handler = handler;
}
public boolean supports(String action) {
return actionMethods.containsKey(action);
}
public void handle(IncomingMessage incomingMessage, RealTimeSession session) {
Assert.isTrue(antPathMatcher.match(channelPattern, incomingMessage.getChannel()), "Channel of the handler must match");
Method actionMethod = actionMethods.get(incomingMessage.getAction());
Assert.notNull(actionMethod, "Action method for `" + incomingMessage.getAction() + "` must exist");
// Find all required parameters
Class<?>[] parameterTypes = actionMethod.getParameterTypes();
// All the annotations for each parameter
Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
// The arguments that will be passed to the action method
Object[] args = new Object[parameterTypes.length];
try {
// Populate arguments
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] parameterAnnotations = allParameterAnnotations[i];
// No annotation applied on this parameter
if (parameterAnnotations.length == 0) {
if (parameterType.isInstance(session)) {
args[i] = session;
} else {
args[i] = null;
}
continue;
}
// Only use the first annotation applied on the parameter
Annotation parameterAnnotation = parameterAnnotations[0];
if (parameterAnnotation instanceof Payload) {
Object arg = JsonUtils.toObject(incomingMessage.getPayload(), parameterType);
if (arg == null) {
throw new IllegalArgumentException("Unable to instantiate parameter of type `" +
parameterType.getName() + "`.");
}
args[i] = arg;
} else if (parameterAnnotation instanceof ChannelValue) {
args[i] = incomingMessage.getChannel();
}
}
actionMethod.invoke(handler, args);
} catch (Exception e) {
String error = "Failed to invoker action method `" + incomingMessage.getAction() +
"` at channel `" + incomingMessage.getChannel() + "` ";
log.error(error, e);
session.error(error);
}
}
}
#Component
public class ChannelHandlerResolver {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerResolver.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
// The key is the channel ant-like path pattern, value is the corresponding invoker
private final Map<String, ChannelHandlerInvoker> invokers = new HashMap<>();
private ApplicationContext applicationContext;
public ChannelHandlerResolver(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.bootstrap();
}
public ChannelHandlerInvoker findInvoker(IncomingMessage incomingMessage) {
ChannelHandlerInvoker invoker = null;
Set<String> pathPatterns = invokers.keySet();
for (String pathPattern : pathPatterns) {
if (antPathMatcher.match(pathPattern, incomingMessage.getChannel())) {
invoker = invokers.get(pathPattern);
}
}
if (invoker == null) {
return null;
}
return invoker.supports(incomingMessage.getAction()) ? invoker : null;
}
private void bootstrap() {
log.info("Bootstrapping channel handler resolver");
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(ChannelHandler.class);
for (String handlerName : handlers.keySet()) {
Object handler = handlers.get(handlerName);
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
String channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
if (invokers.containsKey(channelPattern)) {
throw new IllegalStateException("Duplicated handlers found for chanel pattern `" + channelPattern + "`.");
}
invokers.put(channelPattern, new ChannelHandlerInvoker(handler));
log.debug("Mapped channel `{}` to channel handler `{}`", channelPattern, handlerClass.getName());
}
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
UPDATE 2
I have managed to make ChannelHandler and Action annotations work by adding #Inherited annotation and using AnnotationUtils.findAnnotation() which traverses its super methods if the annotation is not directly present on the given method itself.
However, I haven't managed to access custom annotation value of type parameter (ChannelValue)
Here, Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
returns null value.
UPDATE 3 -> SOLVED
Just add #Aspect annotation to your ChannelHandler implementation (e.g.
"BoardChannelHandler").
Looks like bootstrap() method, that goes through all the #ChannelHandler annotated beans is executed too early - try to debug it to check if it detects any beans at this stage.
If not try calling bootstrap() after Spring context is ready (for example listen for ContextRefreshedEvent.

Cucumber Guice / Injector seems not to be thread-safe (Parallel execution / ExecutorService)

[long description warning]
I'm running some cucumber tests which have to be executed intercalated a defined server - for instance:
a.feature -> JBoss Server 1 | b.feature -> JBoss Serv. 2 | c.feature -> JB1 | etc.
For that, I created a hypothetical ExecutorService like this:
final ExecutorService executorService = Executors.newFixedThreadPool(2); //numberOfServers
for (Runnable task : tasks) {
executorService.execute(task);
}
executorService.shutdown();
try {
executorService.awaitTermination(1000, TimeUnit.SECONDS);
} catch (InterruptedException e) {
//doX();
}
The way that I manage about how will be the server chosen as liable to execute is:
inside of my Runnable class created for the executorService, I pass as a parameter a instanceId to a TestNG (XmlTest class) as below:
#Override
public void run() {
setupTest().run();
}
private TestNG setupTest() {
TestNG testNG = new TestNG();
XmlSuite xmlSuite = new XmlSuite();
XmlTest xmlTest = new XmlTest(xmlSuite);
xmlTest.setName(//irrelevant);
xmlTest.addParameter("instanceId", String.valueOf(instanceId));
xmlTest.setXmlClasses(..........);
testNG.setXmlSuites(..........);
return testNG;
}
Then, I get this just fine in a class that extends TestNgCucumberAdaptor:
#BeforeTest
#Parameters({"instanceId"})
public void setInstanceId(#Optional("") String instanceId) {
if (!StringUtils.isEmpty(instanceId)) {
this.instanceId = Integer.valueOf(instanceId);
}
}
And inside a #BeforeClass I'm populating a Pojo with this instanceId and setting the Pojo in a threadLocal attribute of another class. So far, so good.
public class CurrentPojoContext {
private static final ThreadLocal<PojoContext> TEST_CONTEXT = new ThreadLocal<PojoContext>();
...
public static PojoContext getContext(){
TEST_CONTEXT.get();
}
Now the problem really starts - I'm using Guice (Cucumber guice as well) in a 3rd class, injecting this pojo object that contains the instanceId. The example follows:
public class Environment {
protected final PojoContext pojoContext;
#Inject
public Environment() {
this.pojoContext = CurrentPojoContext.getContext();
}
public void foo() {
print(pojoContext.instanceId); // output: 1
Another.doSomething(pojoContext);
}
class Another{
public String doSomething(PojoContext p){
print(p.instanceId); // output: 2
}
}
}
Here it is not every time like this the outputs (1 and 2) but from time to time, I realized that the execution of different threads is messing with the attribute pojoContext. I know that is a little confusing, but my guess is that the Guice Injector is not thread-safe for this scenario - it might be a long shot, but I'd appreciate if someone else takes a guess.
Regards
Well, just in order to provide a solution for someone else, my solution was the following:
Create a class that maintains a Map with an identifier (unique and thread-safe one) as the key and a Guice Injector as value;
Inside my instantiation of Guice injector, I created my own module
Guice.createInjector(Stage.PRODUCTION, MyOwnModules.SCENARIO, new RandomModule());
and for this module:
public class MyOwnModules {
public static final Module SCENARIO = new ScenarioModule(MyOwnCucumberScopes.SCENARIO);
}
the scope defined here provides the following:
public class MyOwnCucumberScopes {
public static final ScenarioScope SCENARIO = new ParallelScenarioScope();
}
To sum up, the thread-safe will be in the ParallelScenarioScope:
public class ParallelScenarioScope implements ScenarioScope {
private static final Logger LOGGER = Logger.getLogger(ParallelScenarioScope.class);
private final ThreadLocal<Map<Key<?>, Object>> threadLocalMap = new ThreadLocal<Map<Key<?>, Object>>();
#Override
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
return new Provider<T>() {
public T get() {
Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key);
#SuppressWarnings("unchecked")
T current = (T) scopedObjects.get(key);
if (current == null && !scopedObjects.containsKey(key)) {
current = unscoped.get();
scopedObjects.put(key, current);
}
return current;
}
};
}
protected <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) {
Map<Key<?>, Object> map = threadLocalMap.get();
if (map == null) {
throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block");
}
return map;
}
#Override
public void enterScope() {
checkState(threadLocalMap.get() == null, "A scoping block is already in progress");
threadLocalMap.set(new ConcurrentHashMap<Key<?>, Object>());
}
#Override
public void exitScope() {
checkState(threadLocalMap.get() != null, "No scoping block in progress");
threadLocalMap.remove();
}
private void checkState(boolean expression, String errorMessage) {
if (!expression) {
LOGGER.info("M=checkState, Will throw exception: " + errorMessage);
throw new IllegalStateException(errorMessage);
}
}
}
Now the gotcha is just to be careful regarding the #ScenarioScoped and the code will work as expected.

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