C
christian.bongiorno
So, I am trying to use a class as a Singleton inside AXIS. The
deployment descriptor has a scope qualifier for "application" which,
per documentation will accomplish this.
However, the class needs to also be able to work as an internal API to
other classes. So, what I have done is to use a lazy instantiation of
the class along with a singleton. I will paste the code. Essentially I
check to see if the AXIS loader is present in the class path, if it is,
then I also check to see who the caller is of my constructor. If it's
AXIS, I allow construction and set my singleton instance to 'this'.
The problem is if the loader class is present, even if it isn't used,
the "getInstance()" method or even the constructor cannot be called
until after AXIS has called it (which, if the class is just in the path
can never happen). Someone may actually need a class that's in the same
jar as the loader for something else they are doing -- then it's game
over.
What I would LOVE is to have AXIS actually use the "getInstance()"
method like everyone else.
Here is the code. To protect my ass certain pieces are ommited, but you
get the idea -- would appreciate some ideas.
Christian
-----------------------------
/**
* Created by IntelliJ IDEA.
* User: bongiornoc
* Date: Oct 31, 2005
* Time: 11:17:12 AM
* To change this template use File | Settings | File Templates.
*/
import com.artesia.commontoolkit.preferences.Preferences;
import com.artesia.commontoolkit.wrappers.DBConnection;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* This object needs to behave like a singleton and also work inside
the apache AXIS framework
* To that end, eventhough we can congigure the scope of this object as
a webservice to a singleton
* in the apache framework, it requires a public default constructor to
do so. However, this is contrary
* to the singleton model where use of a private constructor is in
order.
* <p/>
* To get around this, we use lazy instantiation in both the public
constructor and the getInstance()
* method. If a secondary attempt to use the constructor is performed,
an exception is thrown, thus
* resulting in no construction -- maintaining our singleton design and
leaving this object compatible
* as a webservice and an internal API
*
*/
public class SomeClass{
private static SomeClass ourInstance = null;
private static boolean inAxisEnv = false;
private static final String AXIS_LOADER_NAME =
"org.apache.axis.providers.java.JavaProvider";
static {
try {
Class axis = Class.forName(AXIS_LOADER_NAME);
inAxisEnv = true;
}
catch (ClassNotFoundException e) {
// if the class was not found it means this class is not
executing within the AXIS framework
// and should be usable by any other class
System.out.println("not in axis env");
}
}
private final char SEPERATOR = '_';
public static SomeClass getInstance() {
if (ourInstance == null)
ourInstance = new SomeClass();
return ourInstance;
}
public static void main(String[] args) throws Exception {
String id = SomeClass.getInstance().getUniqueId(args[0],
args[1], args[2], args[3]);
System.out.println(id);
System.out.println("this should throw an exception");
new SomeClass();
}
/**
* This constructor can only be sucessfully called once and only
under specific situations.
* upon load, this class attempts to determine if we are in the
AXIS operating ENV. If we are, then
* ONLY axis can successfully call this constructor, and AXIS must
be the first. If we are not in the AXIS
* env then anyone can make the call, but only once. After that,
getInstance() must be called to get the instance
* @see #getInstance()
*/
public SomeClass() {
// if this class has already been loaded then throw an
exception -- singleton only
if (ourInstance != null)
throw new RuntimeException("The Id generate has already
been loaded");
else {
// if the axis loader actually is part of this operating
env, then it's POSSIBLE
// that AXIS is trying to invoke this constructor and
that's ok.
if(inAxisEnv) {
// if AXIS is trying to summon us, then all is well.
Let it use this constructor -- but only once
if(isAxisCalling())
ourInstance = this;
else // if we're in an AXIS env, then AXIS must load us
first
throw new RuntimeException("When running in an AXIS
env AXIS must first summon this service");
}
else // if AXIS is not part of our env, then clearly this
class is operating standalone and thus
ourInstance = this;// we can allow construction to our
caller regardless of who it is.
}
}
/**
* determine if axis is somewhere in the call stack. Used to
determine if we should allow construction
* @return true if the AXIS_LOADER_NAME is a calling class in the
stack
*/
private boolean isAxisCalling() {
boolean axisCallFound = false;
StackTraceElement[] stack = new Throwable().getStackTrace();
for (int i = 0; !axisCallFound && i < stack.length; i++)
axisCallFound =
stack.getClassName().equals(AXIS_LOADER_NAME);
return axisCallFound;
}
/**
* returns a unique id based upon the input parameters and salted
with a random value.
*
* @param source
* @param title
* @param startDate
* @param startTime
* @return
*/
public String getUniqueId(String source, String title, String
startDate, String startTime) throws SomeException{
String retVal = null;
Connection conn = null;
String errMsg = "There was an error trying to create your ID.
If the problem persists, contact your admin ";
// once this part has been changed, the only thing that could
possible varry is the random
StringBuffer baseValue = new StringBuffer(generateBase(source,
title, startDate, startTime).toString());
String random = SomeOtherClass.randomLong();
String selectStatement = buildSelectStatement(baseValue,
random);
DBConnection dbc = DBConnection.getInstance();
try {
conn = dbc.getConnection();
Statement checkStat = conn.createStatement();
ResultSet results =
checkStat.executeQuery(selectStatement);
// in the absolutely rediculous chance that we have an
equal id in the DB, we try until that is false
while (results != null && results.next()) {
random = SomeOtherClass.randomLong();
selectStatement = buildSelectStatement(baseValue,
random);
results = checkStat.executeQuery(selectStatement);
if (results == null)
throw new SomeException(errMsg);
}
retVal = baseValue + random;
}
catch (SQLException e) {
e.printStackTrace();
throw new SomeException(errMsg + e);
}
finally {
if (conn != null)
dbc.releaseConnection(conn);
}
return retVal;
}
private String buildSelectStatement(StringBuffer baseValue, String
random) {
return new
StringBuffer(BASE_SELECT).append(baseValue).append(random).append('\'').toString();
}
private StringBuffer generateBase(String source, String title,
String startDate, String startTime) {
StringBuffer base = new
StringBuffer(siteName).append(SEPERATOR);
base.append(source).append(SEPERATOR).append(title).append(SEPERATOR).append(startDate).append(SEPERATOR).append(startTime);
return base.append(SEPERATOR);
}
}
deployment descriptor has a scope qualifier for "application" which,
per documentation will accomplish this.
However, the class needs to also be able to work as an internal API to
other classes. So, what I have done is to use a lazy instantiation of
the class along with a singleton. I will paste the code. Essentially I
check to see if the AXIS loader is present in the class path, if it is,
then I also check to see who the caller is of my constructor. If it's
AXIS, I allow construction and set my singleton instance to 'this'.
The problem is if the loader class is present, even if it isn't used,
the "getInstance()" method or even the constructor cannot be called
until after AXIS has called it (which, if the class is just in the path
can never happen). Someone may actually need a class that's in the same
jar as the loader for something else they are doing -- then it's game
over.
What I would LOVE is to have AXIS actually use the "getInstance()"
method like everyone else.
Here is the code. To protect my ass certain pieces are ommited, but you
get the idea -- would appreciate some ideas.
Christian
-----------------------------
/**
* Created by IntelliJ IDEA.
* User: bongiornoc
* Date: Oct 31, 2005
* Time: 11:17:12 AM
* To change this template use File | Settings | File Templates.
*/
import com.artesia.commontoolkit.preferences.Preferences;
import com.artesia.commontoolkit.wrappers.DBConnection;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* This object needs to behave like a singleton and also work inside
the apache AXIS framework
* To that end, eventhough we can congigure the scope of this object as
a webservice to a singleton
* in the apache framework, it requires a public default constructor to
do so. However, this is contrary
* to the singleton model where use of a private constructor is in
order.
* <p/>
* To get around this, we use lazy instantiation in both the public
constructor and the getInstance()
* method. If a secondary attempt to use the constructor is performed,
an exception is thrown, thus
* resulting in no construction -- maintaining our singleton design and
leaving this object compatible
* as a webservice and an internal API
*
*/
public class SomeClass{
private static SomeClass ourInstance = null;
private static boolean inAxisEnv = false;
private static final String AXIS_LOADER_NAME =
"org.apache.axis.providers.java.JavaProvider";
static {
try {
Class axis = Class.forName(AXIS_LOADER_NAME);
inAxisEnv = true;
}
catch (ClassNotFoundException e) {
// if the class was not found it means this class is not
executing within the AXIS framework
// and should be usable by any other class
System.out.println("not in axis env");
}
}
private final char SEPERATOR = '_';
public static SomeClass getInstance() {
if (ourInstance == null)
ourInstance = new SomeClass();
return ourInstance;
}
public static void main(String[] args) throws Exception {
String id = SomeClass.getInstance().getUniqueId(args[0],
args[1], args[2], args[3]);
System.out.println(id);
System.out.println("this should throw an exception");
new SomeClass();
}
/**
* This constructor can only be sucessfully called once and only
under specific situations.
* upon load, this class attempts to determine if we are in the
AXIS operating ENV. If we are, then
* ONLY axis can successfully call this constructor, and AXIS must
be the first. If we are not in the AXIS
* env then anyone can make the call, but only once. After that,
getInstance() must be called to get the instance
* @see #getInstance()
*/
public SomeClass() {
// if this class has already been loaded then throw an
exception -- singleton only
if (ourInstance != null)
throw new RuntimeException("The Id generate has already
been loaded");
else {
// if the axis loader actually is part of this operating
env, then it's POSSIBLE
// that AXIS is trying to invoke this constructor and
that's ok.
if(inAxisEnv) {
// if AXIS is trying to summon us, then all is well.
Let it use this constructor -- but only once
if(isAxisCalling())
ourInstance = this;
else // if we're in an AXIS env, then AXIS must load us
first
throw new RuntimeException("When running in an AXIS
env AXIS must first summon this service");
}
else // if AXIS is not part of our env, then clearly this
class is operating standalone and thus
ourInstance = this;// we can allow construction to our
caller regardless of who it is.
}
}
/**
* determine if axis is somewhere in the call stack. Used to
determine if we should allow construction
* @return true if the AXIS_LOADER_NAME is a calling class in the
stack
*/
private boolean isAxisCalling() {
boolean axisCallFound = false;
StackTraceElement[] stack = new Throwable().getStackTrace();
for (int i = 0; !axisCallFound && i < stack.length; i++)
axisCallFound =
stack.getClassName().equals(AXIS_LOADER_NAME);
return axisCallFound;
}
/**
* returns a unique id based upon the input parameters and salted
with a random value.
*
* @param source
* @param title
* @param startDate
* @param startTime
* @return
*/
public String getUniqueId(String source, String title, String
startDate, String startTime) throws SomeException{
String retVal = null;
Connection conn = null;
String errMsg = "There was an error trying to create your ID.
If the problem persists, contact your admin ";
// once this part has been changed, the only thing that could
possible varry is the random
StringBuffer baseValue = new StringBuffer(generateBase(source,
title, startDate, startTime).toString());
String random = SomeOtherClass.randomLong();
String selectStatement = buildSelectStatement(baseValue,
random);
DBConnection dbc = DBConnection.getInstance();
try {
conn = dbc.getConnection();
Statement checkStat = conn.createStatement();
ResultSet results =
checkStat.executeQuery(selectStatement);
// in the absolutely rediculous chance that we have an
equal id in the DB, we try until that is false
while (results != null && results.next()) {
random = SomeOtherClass.randomLong();
selectStatement = buildSelectStatement(baseValue,
random);
results = checkStat.executeQuery(selectStatement);
if (results == null)
throw new SomeException(errMsg);
}
retVal = baseValue + random;
}
catch (SQLException e) {
e.printStackTrace();
throw new SomeException(errMsg + e);
}
finally {
if (conn != null)
dbc.releaseConnection(conn);
}
return retVal;
}
private String buildSelectStatement(StringBuffer baseValue, String
random) {
return new
StringBuffer(BASE_SELECT).append(baseValue).append(random).append('\'').toString();
}
private StringBuffer generateBase(String source, String title,
String startDate, String startTime) {
StringBuffer base = new
StringBuffer(siteName).append(SEPERATOR);
base.append(source).append(SEPERATOR).append(title).append(SEPERATOR).append(startDate).append(SEPERATOR).append(startTime);
return base.append(SEPERATOR);
}
}