Spring 3.1 and Oracle Audit Trail: Providing application data to triggers - spring

Problem Parameters:
Spring 3.1
Oracle 11.2.0.3
Glassfish 2.1 Application Server, providing a JDBC connection pool.
Problem Description:
I am retrofitting user auditing in an existing set of administrative applications for adding, editing and deleting customer users. I need to store the ID of the administrative user in audit records created by Oracle triggers associated with a number of tables. I want to make the administrative user Id accessible to the triggers by setting the Oracle CLIENT_IDENTIFIER attribute on each a connection retrieved from the connection pool before a database operation and then clear the attribute after the database operation. I have something that works, but I don't really like the way it is done.
The Question:
Is there a way to access connections so an Oracle context attribute can be set before and after a database operation? Maybe some kind of listener responding to an event?
I have looked at:
A million web pages (OK maybe that's an exaggeration, but I've googled for a three or four hours).
Using DataSourceUtils to get connections. This would work, but I really don't want to manage the connections, I just want to intercept them on the way in and out of the pool to set the CLIENT_IDENTIFIER attribute value.
Overriding the getConnection method of the datasource. Since this gets called somewhere inside the JdbcTemplate, I can't get the application data to the method.
I'm hoping that the Spring and/or Oracle Gurus will say "Well it's obvious and the answer is ... " without having to hack through my implementation, but here it is anyway. If nothing else, this does work in case someone is looking for an idea.
My Implementation:
All database operations are done using a JdbcOperations reference to a JdbcTemplate object injected into a Dao. The add, edit and delete operation use the JdbcOperations query method, passing either a PreparedStatementCreator or a BatchPreparedStatementSetter. I access the java.sql.Connection object provided by the application server connection pool in the callback methods for these objects (createPreparedStatement or setValues) to set the CLIENT_IDENTIFIER attribute.
applicationContext.xml datasource configuration:
<!-- Setup the datasource -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/IpOneDatabasePool"/>
</bean>
<!-- Setup the transaction manager -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- Associate the transaction manager with objects that must be managed. -->
<aop:config>
<aop:pointcut id="userDaoOperation" expression="execution(* com.myCompany.IpOne.dao.UserDaoImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="userDaoOperation"/>
</aop:config>
<!-- Bean providing access to the various prepared statement objects -->
<bean id="daoHelperFactory" class="com.myCompany.IpOne.dao.DaoHelperFactoryImpl" />
<!-- Bean that allows setting of the client identifier for the audit trail -->
<bean id="databaseContextEditor" class="com.myCompany.IpOne.dao.OracleDatabaseContextEditor" />
<!-- Dao that manages persistence of User objects -->
<bean id="userDao" class="com.myCompany.IpOne.dao.UserDaoImpl" >
<property name="dataSource" ref="dataSource"/>
<property name="licenseDao" ref="licenseDao"/>
<property name="appPropertyManager" ref="appPropertyManager"/>
<property name="maximumLicensesPerUserKey" value="max_licences_per_user"/>
<property name="daoHelperFactory" ref="daoHelperFactory"/>
</bean>
This is the user Dao interface
public interface UserDao {
void addUser(User newUser,String adminUserId);
}
This is the user Dao class
public class UserDaoImpl implements UserDao{
private JdbcOperations jdbcOperations;
public void setDataSource(DataSource dataSource) {
this.jdbcOperations = new JdbcTemplate(dataSource);
}
public void addUser(User newUser,String adminUserId) {
PreparedStatementCreator insertUserStatement =
this.daoHelperFactory.getInsertUserStatement(newUser,adminUserId);
KeyHolder keyHolder = this.daoHelperFactory.getKeyHolder();
this.jdbcOperations.update(insertUserStatement, keyHolder);
newUser.setUserId(keyHolder.getKey().intValue());
}
}
This class provides access to the application context.
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return ctx;
}
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
}
Interface for classes that provides various objects used by the Dao.
public interface DaoHelperFactory {
PreparedStatementCreator getInsertUserStatement(User user,String adminUserId);
KeyHolder getKeyHolder();
}
This class is just a factory for PreparedStatementCreator and BatchPreparedStatementSetter objects and other objects used by the Dao. I've changed it to provide the object that actually sets the database context attribute to the various objects being returned.
public class DaoHelperFactoryImpl implements DaoHelperFactory{
private DatabaseContextEditor getDatabaseContextEditor(){
ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
DatabaseContextEditor databaseContextEditor = (DatabaseContextEditor) appContext.getBean("databaseContextEditor");
return databaseContextEditor;
}
public KeyHolder getKeyHolder(){
return new GeneratedKeyHolder();
}
public PreparedStatementCreator getInsertUserStatement(User user,String adminUserId){
InsertUser insertUser = new InsertUser(user,adminUserId);
insertUser.setDatabaseContextEditor(getDatabaseContextEditor());
return insertUser;
}
}
This is the interface for classes that set the database context
public interface DatabaseContextEditor {
public DatabaseContextEditor getInstance();
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException;
}
This is class which does that for Oracle
public class OracleDatabaseContextEditor implements DatabaseContextEditor{
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException{
OracleJdbc4NativeJdbcExtractor extractor = new OracleJdbc4NativeJdbcExtractor();
oracle.jdbc.OracleConnection oracleConnection = null;
if(!(connection instanceof oracle.jdbc.OracleConnection))
oracleConnection = (oracle.jdbc.OracleConnection) extractor.getNativeConnection(connection);
else
oracleConnection = (oracle.jdbc.OracleConnection)connection;
String[] metrics = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX]=clientIdentifier;
oracleConnection.setEndToEndMetrics(metrics,(short)0);
}
public DatabaseContextEditor getInstance(){
return new OracleDatabaseContextEditor();
}
}
This class is the PreparedStatementCreator for adding a User
public class InsertUser implements PreparedStatementCreator {
User insertUser;
/** This is the admin user Id I need to store */
String adminUserId;
private final String SQL = "INSERT INTO SC_USR (" +
"USR_ID, USR_SSO_NAME, USR_PH_NO, USR_SIP_NAME," +
"USR_SIP_PSWD, USR_SIP_DISP_NAME, USR_SIP_DOMAIN, USR_SIP_PROXY," +
" USR_CREATED_BY, USR_CREATED_DATETIME) " +
"VALUES (SEQ_SC_USR_ID.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, SYSTIMESTAMP)";
private final String GENERATED_COLUMNS[] = {"USR_ID"};
/** Object that provides functionality for setting values in the database context */
private DatabaseContextEditor databaseContextEditor;
public InsertUser(User user,String adminUserId){
this.insertUser = user;
this.adminUserId = adminUserId;
}
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
this.databaseContextEditor.setClientIdentifier(connection, adminUserId);
PreparedStatement preparedStatement = connection.prepareStatement(SQL,GENERATED_COLUMNS);
int i=1;
preparedStatement.setString(i++,this.insertUser.getSsoName());
preparedStatement.setString(i++,this.insertUser.getPhoneNumber());
preparedStatement.setString(i++,this.insertUser.getSipName());
preparedStatement.setString(i++,this.insertUser.getSipPassword());
preparedStatement.setString(i++,this.insertUser.getSipDisplayName());
preparedStatement.setString(i++,this.insertUser.getSipDomain());
preparedStatement.setString(i++,this.insertUser.getSipProxy());
preparedStatement.setString(i++,this.insertUser.getCreatedBy().name());
return preparedStatement;
}
public void setDatabaseContextEditor(DatabaseContextEditor databaseContextEditor) {
this.databaseContextEditor = databaseContextEditor;
}
}
There are "AFTER DELETE OR INSERT OR UPDATE" triggers on each table I want to audit. Each table has a corresponding audit table. They extract the CLIENT_IDENTIFIER from the context and insert a row in the appropriate audit table. This is a sample.
CREATE OR REPLACE TRIGGER IPONE_DEV_USER.SC_USR$AUDTRG
AFTER DELETE OR INSERT OR UPDATE
ON IPONE_DEV_USER.SC_USR
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10) := NULL;
v_admin_user_id VARCHAR2(30);
BEGIN
v_admin_user_id := SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER');
IF INSERTING THEN
v_operation := 'INS';
ELSIF UPDATING THEN
v_operation := 'UPD';
ELSE
v_operation := 'DEL';
END IF;
IF INSERTING OR UPDATING THEN
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SOME_VALUE1,
USR_SOME_VALUE2,
USR_SOME_VALUE3,
USR_SOME_VALUE4,
USR_CREATED_BY,
USR_SOME_VALUE5,
USR_SOME_VALUE6,
aud_action,aud_timestamp,aud_user) VALUES (
:new.USR_ID,
:new.USR_SSO_NAME,
:new.USR_PH_NO,
:new.USR_SOME_VALUE1,
:new.USR_SOME_VALUE2,
:new.USR_SOME_VALUE3,
:new.USR_CREATED_DATETIME,
:new.USR_CREATED_BY,
:new.USR_SOME_VALUE4,
:new.USR_SOME_VALUE5,
v_operation,SYSDATE,v_admin_user_id);
ELSE
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SIP_NAME,
USR_SIP_PSWD,
USR_SIP_DISP_NAME,
USR_CREATED_DATETIME,
USR_CREATED_BY,
USR_SIP_DOMAIN,
USR_SIP_PROXY,
aud_action,aud_timestamp,aud_user) VALUES (
:old.USR_ID,
:old.USR_SSO_NAME,
:old.USR_PH_NO,
:old.USR_SIP_NAME,
:old.USR_SIP_PSWD,
:old.USR_SIP_DISP_NAME,
:old.USR_CREATED_DATETIME,
:old.USR_CREATED_BY,
:old.USR_SIP_DOMAIN,
:old.USR_SIP_PROXY,
v_operation,SYSDATE,v_admin_user_id);
END IF;
END;
As I say this works, but I don't like it for the following reasons.
I have to modify the connection in methods that are intended for setting up the prepared statements.
I have to add this code to every PreparedStatementCreator or a BatchPreparedStatementSetter object I want to audit.
I don't have access to the connection after the database operation so I can clear the attribute.
What I really want is a single point where I can set the attribute on the connection, before and after.
Any input or ideas would be appreciated.

Spring have an elegant way of doing this. The example is pretty much what you want:
Spring Data docs
Uses AOP to set the CLIENT_IDENTIFIER when a connection is got from the Datasource.
Could use another pointcut for when the connection is closed. But not a problem is the connection pool is used soley by your app.

A better way is to use connection labeling. Have a look at the oracle.ucp.jdbc.LabelableConnection interface.

Related

Spring bean how to access server resource only once?

I have a Spring bean which refers connection factory to access J2C resource. This code is deployed in webSphere server in 2 jvms load balanced. I had the readAuth method as init-method in bean xml but it was called multiple times during a load test. Since Spring singleton is per bean per container, I assumed there are multiple containers causing it to load multiple times. So I removed the init-method and changed the username and password to static and added null check in get methods. But now also, readAuth method is being called multiple times. I want to ensure this method is called only once per jvm since this method access server resource and connection is timing out during load test.
Please suggest a best approach on how to write this class. Thanks in advance.
<bean id="J2CUtils"
class="test.J2CUtils">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/J2CAuth" />
public class J2Ctils {
private ConnectionFactory connectionFactory;
private static String userName;
private static String password;
private void readAuth() throws ResourceException {
System.out.println("Auth loaded");
Connection conn = connectionFactory.getConnection();
Interaction interaction = (Interaction) conn.createInteraction();
Config config = interaction.getConfig();
userName = config.getUserName();
password = config.getPassword();
}
public String getUserName() {
if(null == userName) {
readAuth();
}
return userName;
}
public String getPassword() {
if(null == password) {
readAuth();
}
return password;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
}
Singletons or static methods don't help if you want to access a resource exactly once because every member of the cluster has it's own instance of the class.
Take in mind that neither the Java EE specification the Spring specification do define any kind of cluster behavior. You need to search for specific vendor solution to achieve this kind of requirement. Just as an example see have a look at #ApplicationScped in a cluster.
Other approaches to archive the expected behaviour are using a distributed lock like Zookeeper or maybe even a Queue with duplicate message filter.

Using one DataSource object for multiple DB connections - using only one connection at a time

Can we define only one DataSource object and wire it dynamically at runtime connecting to different databases ? I need to connect to only one database at a time.
I will be passing the name of the Database as argument. I will lookup the DB URL and other details from a property file and then I need to connect to the DB using the DB URL.
In Short - I do not know the number of databases I need to connect to. I will have all possible database connection details configured in the database.properties file following a certain syntax (like prefixed with DB01 etc.). The name of the DB will be passed as argument and I need to execute the query against that database.
database.properties file
DB01.driver=com.ibm.db2.jcc.DB2Driver
DB01.url=jdbc:db2://localhost:50000/SAMPLE
DB01.username=db2admin
DB01.password=db2admin
DAO class
#Autowired
#Qualifier("DB01") // how do I make this dynamic ?
private DataSource datasource;
private JdbcTemplate jdbcTemplate;
// some more code
public SqlRowSet executeQuery(String sqlQuery)
{
// can I pass the DB name here (the database.properties file will have the DB details
// with this name as given above) and set the DataSource Object accordingly ?
// so that the query will be executed against that DB ?
setJdbcTemplate(new JdbcTemplate(this.datasource));
return getJdbcTemplate().queryForRowSet(sqlQuery);
}
Using Spring v4.1.4 RELEASE. Thanks !!
You can define a Routing DataSource that redirects the getConnection method to one datasource or another based on a key, in your case, it seems to be the database name.
For instance, the spring xml:
....
<bean id="DB01DataSource" parent="parentDatasource" p:url="${DB01.url}" ... />
<bean id="DB02DataSource" parent="parentDatasource" p:url="${DB02.url}" .../>
<bean id="dataSource" class="DBRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="DB01" value-ref="DB01DataSource"/>
<entry key="DB02" value-ref="DB02DataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="DB01DataSource"/>
</bean>
....
The DBRoutingDataSource class:
public class DBRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.getDB();
}
}
And the DBContextHolder class:
public final class DBContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<String>();
private DBContextHolder() {
// empty
}
public static void setDB(final String db) {
CONTEXT.set(db);
}
public static String getDB() {
return CONTEXT.get();
}
public static void clearDB() {
CONTEXT.remove();
}
}
In your service class before calling your DAO you set the key that will enable the Routing DataSource to get the right connection:
DBContextHolder.setDB("DB01");
try{
dao.executeQuery(sqlSentence);
}finally{
DBContextHolder.clearDB();
}

How to use low-level driver APIs with Spring Data MongoDB

I am using Spring Data MongoDB. But I don't want to map my result to a domain class. Also, I want to access low level MongoAB APIs in few cases. But I want spring to manage the connections pooling etc.
How can i get an instance of com.mongodb.MongoClient to perform low level operations.
Here is what I am trying to do :
MongoClient mongoClient = new MongoClient();
DB local = mongoClient.getDB("local");
DBCollection oplog = local.getCollection("oplog.$main");
DBCursor lastCursor = oplog.find().sort(new BasicDBObject("$natural", -1)).limit(1);
Or I simply want a JSON object / DBCursor / DBObject.
you can do it this way
#Autowired MongoDbFactory factory;
DB local = factory.getDB("local");
DBCollection oplog = local.getCollection("oplog.$main");
DBCursor lastCursor = oplog.find().sort(new BasicDBObject("$natural", -1)).limit(1);
Where
MongoDbFactory is an interface provifed by spring-data-mongo that can obtain a
com.mongodb.DB object and access allthe functionality of a specific MongoDB database
instance
your configuration file should contain these informations :
<bean id="mongoFactoryBean"
class="org.springframework.data.mongodb.core.MongoFactoryBean">
<property name="host" value="127.0.0.1"/>
<property name="port" value="27017"/>
</bean>
<bean id="mongoDbFactory"
class="org.springframework.data.mongodb.core.SimpleMongoDbFactory">
<constructor-arg name="mongo" ref="mongoFactoryBean"/>
<constructor-arg name="databaseName" value="local"/>
</bean>
doing it like that, spring should stay managing your connection pool.
You usually perform low level access through MongoTemplate's execute(…) methods that take callbacks giving you access to the native Mongo driver API.
class MyClient {
private final MongoOperations operations;
#Autowired
public MyClient(MongoOperations mongoOperations) {
this.operations = operations;
}
void yourMethod() {
operations.execute(new CollectionCallback<YourDomainClass>() {
YourDomainClass doInCollection(DBCollection collection) {
// here goes your low-level code
}
});
}
The advantage of this template approach is that the MongoTemplate instance backing the MongoOperations interface will still take care of all resource management and exception translation (converting all Mongo specific exceptions into Spring's DataAccessException hierarchy).
However, for your concrete example you could just go ahead and do the following directly:
Query query = new Query().with(new Sort(DESC, "$natural")).limit(1);
DBObject result = operations.find(query, DBObject.class, "oplog.$main");
Here you can mix and match the type you pass into the find(…) method to let the template convert the result into a Map or a domain object if needed. As indicated above you also get resource management and exception translation which your sample code above is missing.

Spring Hibernate JPA not saving multiple rows in a same table

I am working on a requirement in which user can send messages to multiple people, I have to save those messages in a message table.
I am using JPA entityManager for persisting objects and Hibernate as a persistence provider and Spring declarative transaction management.
Even I am executing the persist() method for three times it is saving only one row in the table. I don't know what should I do to save all the messages. It is not displaying any exception message.
Following is my declarative transaction management configuration in applicationContext.xml
<tx:advice id="txAdvice" >
<tx:attributes>
<tx:method
name="*"
propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut
id="messageServiceOperation"
expression="execution(* com.abhinow.message.services.*.*(..))" />
<aop:advisor
advice-ref="txAdvice"
pointcut-ref="messageServiceOperation" />
</aop:config>
Following is code in my service class MessageService.java
private void saveMultipleMessages(SentMessage message) {
String[] toAddresses = message.getMultipleDestinations().split(",");
for(String to: toAddresses) {
message.setTo(to);
saveMessage(message);
}
}
public void saveMessage(SentMessage message) {
messageRepository.saveSentMessage(message);
}
Following is code in my MessageRepository.java
#Repository
public class MessageRepository {
#PersistenceContext
EntityManager entityManagerFactory;
public void saveSentMessage(SentMessage message) {
entityManagerFactory.persist(message);
}
}
Any help would be appreciated. Thanks in advance
It looks like you are re-using the same SentMessage object in your for loop. My guess is that JPA will detect that it already persisted this object and will not persist it again.
Try something like this:
private void saveMultipleMessages(SentMessage message) {
String[] toAddresses = message.getMultipleDestinations().split(",");
for(String to: toAddresses) {
SentMessage toSave = message.withToAddress(to);
saveMessage(toSave);
}
}
Where "withToAddress" creates a new SentMessage object based on the current one, but with a different to address. When you use this technique, you can make SentMessage immutable, which often has advantages over mutable objects.

Spring: Inject bean depended on context (session/web or local thread/background process)

Is it possible to create a factory or proxy that can decide if thread is running in (Web)Request or background-process (ie. scheduler) and then depending on that information, it creates a session bean or a prototype bean?
Example (pseudo Spring config :)
<bean id="userInfoSession" scope="session" />
<bean id="userInfoStatic" scope="prototype" />
<bean id="currentUserInfoFactory" />
<bean id="someService" class="...">
<property name="userInfo" ref="currentUserInfoFactory.getCurrentUserInfo()" />
</bean>
I hope this makes my question easier to understand...
My Solution
It's never to late to update own questions ;). I solved it with two different instances of client session, one SessionScoped client session and one SingletonScoped session. Both are normal beans.
<bean id="sessionScopedClientSession" class="com.company.product.session.SessionScopedClientSession" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="singletonScopedClientSession" class="com.company.product.session.SingletonScopedClientSession" />
<bean id="clientSession" class="com.company.product.session.ClientSession">
<property name="sessionScopedClientSessionBeanName" value="sessionScopedClientSession" />
<property name="singletonScopedClientSessionBeanName" value="singletonScopedClientSession" />
</bean>
The ClientSession will then decide if singleton or session scope:
private IClientSession getSessionAwareClientData() {
String beanName = (isInSessionContext() ? sessionScopedClientSessionBeanName : singletonScopedClientSessionBeanName);
return (IClientSession) ApplicationContextProvider.getApplicationContext().getBean(beanName);
}
Where session type could be gathered through this:
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
All the classes implement a interface called IClientSession. Both singletonScoped and sessionScoped beans extends from a BaseClientSession where the implementation is found.
Every service then can use the client session ie:
#Resource
private ClientSession clientSession;
...
public void doSomething() {
Long orgId = clientSession.getSomethingFromSession();
}
Now if we go one step further we can write something like a Emulator for the session. This could be done by initializing the clientSession (which is in no context of a request) the singleton session. Now all services can use the same clientSession and we still can "emulate" a user ie:
clientSessionEmulator.startEmulateUser( testUser );
try {
service.doSomething();
} finally {
clientSessionEmulator.stopEmulation();
}
One more advice: take care about threading in SingletonScoped clientSession instance! Wouw, I thought I could do it with less lines ;) If you like to know more about this approach feel free to contact me.
I created small universal workaround to inject beans depends on context.
Guess we have two beans:
<bean class="xyz.UserInfo" id="userInfo" scope="session" />
<bean class="xyz.UserInfo" id="userInfoSessionLess" />
We want to use "userInfo" bean for web user actions and "userInfoSessionLess" bean for background services for example.
Wa also want to write code and don't want to think about context, for example:
#Autowired
//You will get "java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request?" for session less services.
//We can fix it and autowire "userInfo" or "userInfoSessionLess" depends on context...
private UserInfo userInfo;
public save(Document superSecureDocument) {
...
superSecureDocument.lastModifier = userInfo.getUser();
...
}
Now we need create custom session scope to make it worked:
public class MYSessionScope extends SessionScope implements ApplicationContextAware {
private static final String SESSION_LESS_POSTFIX = "SessionLess";
private ApplicationContext applicationContext;
public Object get(String name, ObjectFactory objectFactory) {
if (isInSessionContext()) {
log.debug("Return session Bean... name = " + name);
return super.get(name, objectFactory);
} else {
log.debug("Trying to access session Bean outside of Request Context... name = " + name + " return bean with name = " + name + SESSION_LESS_POSTFIX);
return applicationContext.getBean(name.replace("scopedTarget.", "") + SESSION_LESS_POSTFIX);
}
}
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Register new scope:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="mySession">
<bean class="com.galantis.gbf.web.MYSessionScope" />
</entry>
</map>
</property>
</bean>
Now we need modify beans definions like this:
<bean class="xyz.UserInfo" id="userInfo" scope="mySession" autowire-candidate="true"/>
<bean class="xyz.UserInfo" id="userInfoSessionLess" autowire-candidate="false"/>
That's all. Bean with name "SessionLess" will be used for all "mySession" scoped beans if we use bean outside of actual web request thread.
Your rephrase is indeed considerably simpler :)
Your currentUserInfoFactory could make use of RequestContextHolder.getRequestAttributes(). If a session is present and associated with the calling thread, then this will return a non-null object, and you can then safely retrieve the session-scoped bean from the context. If it returns a null, then you should fetch the prototype-scoped bean instead.
It's not very neat, but it's simple, and should work.
Create two custom context loaders that bind the same scope defintion to different implementations:
public final class SessionScopeContextLoader extends GenericXmlContextLoader {
protected void customizeContext(final GenericApplicationContext context) {
final SessionScope testSessionScope = new SessionScope();
context.getBeanFactory().registerScope("superscope", testSessionScope);
}
...
}
Then you make a corresponding one for singleton (make your own scope with just statics)
Then you just specify the appropriate context loader in the xml startup for each of the two contexts.

Resources