Solved

Wrapper for Java.sql.statement ?????

Posted on 2002-05-13
6
243 Views
Last Modified: 2012-05-04
Hi all !!

I am going to create a Audit log - and i was just thinking is it possible to somehow create a new statement where i can include a logging routine every time i send a sql statement to the server - I would have to do a lot of changes in my code - so if i could just replace the java.sql.statement with my own statement that would be nice.........

is this possible - i have noticed that a statement is not a class but an interface

any other suggestions on how to do this the smartest way

regards

kim

0
Comment
Question by:kwang080897
  • 3
  • 2
6 Comments
 
LVL 1

Accepted Solution

by:
BaneBane earned 100 total points
ID: 7005725
Why don't you create a static class which will handle all of your database interaction. you could add some update/query methods to it and use these method to log your sql etc.

I have a class like this I could send you if you'd like.

Hope this helps.
0
 

Author Comment

by:kwang080897
ID: 7008343
Thanks !

I would very much like that

regards

email kwangha@hotmail.com
kim

0
 
LVL 1

Expert Comment

by:BaneBane
ID: 7010336
Well this is the code.
Mind you I took it from another program I once wrote, so you may find some unwanted method in there.

What you are basically looking for is the executeQuery(..) and executeUpdate(...) method you could add the log events there.

import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;
import java.io.*;
import java.sql.*;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Collections;
import java.util.Map;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.Date;
import java.util.Properties;
import javax.sql.*;
import javax.naming.*;



/**
*
* An SQLRunner object is used for executing a static SQL statement and obtaining
* the results produced by it.
*
*
* @pre InitialContext exists
*
*/
public class SQLRunner {

    private static DataSource dataSource                    = null;

    /**
    * Returns a stored procedure object.
    *
    * Parameters:
    *
    * @param String  sp - the stored procedure to be executed.
    *
    *
    * @return a CallableStatement object for manipulation by the user.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    *
    */
    public static CallableStatement getStoredProcedure(String sp) throws SQLException{

        Connection connection  = getConnection();
        return connection.prepareCall (sp);

    } //getStoredProcedure



    /**
    * Executes a stored procedure stored in the databaseSQL which returns a single ResultSet.
    * Parameters:
    *
    * @param String  sp - the stored procedure to be executed.
    * @param ArrayList outParams - a list of OUT parameters to be passed to the stored procedure.
    * @param ArrayList inParams  - a list of IN parameters to be passed to the stored procedure.
    *
    * Currently only the following IN parameters are supported:
    *
    *   boolean
    *   TinyInt
    *   BigInt
    *   Null
    *   Integer
    *   SmallInt
    *   Float
    *   Double
    *   VarChar
    *   Date
    *   Time
    *
    * @return a ResultSet that contains the data produced by the stored procedure; never null
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static ResultSet executeSPQuery(String sp, ArrayList outParams, ArrayList inParams) throws SQLException{

        // NOTE: The first array should be an array of integers, the second one sould be and array of Objects
        // Array<SQL.Types[]> outParams
        // Array<SQL.Types[]> inParams


        int nSize       = 0;
        int nPos        = 0;
        int nObjectType = 0;

        Connection connection  = getConnection();
        CallableStatement stmt = connection.prepareCall (sp);

        stmt.registerOutParameter(1,OracleTypes.CURSOR);

        nSize = outParams.size();

        for (nPos = 2 ; nPos < nSize+2; nPos++){

            nObjectType = ((Integer)outParams.get(nPos)).intValue();

            // Step 1 add out variables
            stmt.registerOutParameter(nPos,nObjectType);

        }//for


        int nSize1 = inParams.size();

        for (nPos = nSize+2 ; nPos < nSize + nSize1 + 2; nPos++){
            // Step 2 add in variables
            switch (nObjectType) {

                case -7: //boolean
                    stmt.setBoolean(nPos,((Boolean)inParams.get(nPos-nSize-2)).booleanValue());
                    break;

                case -6: //TinyInt
                    stmt.setByte(nPos, (((Byte)inParams.get(nPos-nSize-2)).byteValue()));
                    break;

                case -5: //BigInt
                    stmt.setLong(nPos, (((Long)inParams.get(nPos-nSize-2)).longValue()));
                    break;

                case 0: //Null
                    stmt.setNull(nPos, 0);
                    break;

                case 4: //Integer
                    stmt.setInt(nPos, (((Integer)inParams.get(nPos-nSize-2)).intValue()));
                    break;

                case 5: //SmallInt
                    stmt.setShort(nPos, (((Short)inParams.get(nPos-nSize-2)).shortValue()));
                    break;

                case 6: //Float
                    stmt.setFloat(nPos, (((Float)inParams.get(nPos-nSize-2)).floatValue()));
                    break;

                case 8: //Double
                    stmt.setDouble(nPos, (((Double)inParams.get(nPos-nSize-2)).doubleValue()));
                    break;

                case 12: //VarChar
                    stmt.setString(nPos, ((String)inParams.get(nPos-nSize-2)));
                    break;

                case 91: //Date
                    stmt.setDate(nPos, ((java.sql.Date)inParams.get(nPos-nSize-2)));
                    break;

                case 92: //Time
                    stmt.setTime(nPos, ((java.sql.Time)inParams.get(nPos-nSize-2)));
                    break;

                default:
                    break;
            } //switch
        } //for


        stmt.execute();

        return ((OracleCallableStatement)stmt).getCursor(1);
    } //executeSPQuery




    /**
    * This method will retrieve a new prepareStatement object, the prepareStatement would be created using the
    * supplied command parameret.
    *
    * The newly created prepareStatement would be generated by using a connection taken from the connection pool.
    *
    * @return PreparedStatement the new PreparedStatement which was created.
    *
    *
    *   A typical use of this method would look like this.
    *
    *      PreparedStatement ps = null ;
    *      try {
    *            ps = SQLRunner.prepareStatement(:select * from USERS_TABLE where MSISDN = ?") ;
    *            ps.setString(1, id) ;
    *            ResultSet rs = ps.executeQuery() ;
    *            ...
    *      } finally {
    *            SQLRunner.release(ps) ;
    *      }
    *
    *
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
      public static PreparedStatement getPrepareStatement(String command) throws SQLException {

          PreparedStatement ps = null;

          Connection connection = getConnection();

          if (command.startsWith("SELECT") || command.startsWith("select"))
                connection.setAutoCommit(false);

          ps = connection.prepareStatement(command);

          return ps;
      }//getPrepareStatement



    /**
    * Executes an SQL statement that returns a single ResultSet.
    * Parameters:
    *
    * @param String  sql - typically this is a static SQL SELECT statement
    *
    * @return a ResultSet that contains the data produced by the query; never null
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    */
    public static ResultSet executeQuery(String sql) throws SQLException{
            return internal_executeQuery(sql,-1,0);
    } //runSQL(String,boolean)



    /**
    * Executes an SQL statement that returns a single ResultSet.
    * Parameters:
    *
    * @param String  sql - typically this is a static SQL SELECT statement
    *
    *
    * @param long  approximateExecutionTime - Approximate Execution Time in mSec for the supplied SQL,
    *        Use this param to prevent the open connection used to execute the supplied SQL from beeing
    *        closed beofre the Approximate Execution Time has passed.
    *
    *        NOTE: The open connection mentioned above MAY not be closed after the Approximate Execution Time
    *              has passed, what is guranteed is that the connection will be closed after the
    *              Approximate Execution Time + the connection TTL (which is defined in the openConnectionGc)
    *              has passed.
    *
    *
    * @return a ResultSet that contains the data produced by the query; never null
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static ResultSet executeQuery(String sql,long approximateExecutionTime) throws SQLException{
            return internal_executeQuery(sql,-1,approximateExecutionTime);
    } //executeQuery




    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    * The SQL execution will be carried out in a new Thread, hence the Execution is A synchronous.
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static void executeUpdateNewThread(String sql) throws SQLException{
        internal_executeUpdateInNewThread(sql);
    } //execute



    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    *
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static int executeUpdate(String sql) throws SQLException{
        return internal_executeUpdate(sql,0);
    } //execute


    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    *
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    *
    * @param long  approximateExecutionTime - Approximate Execution Time in mSec for the supplied SQL,
    *        Use this param to prevent the open connection used to execute the supplied SQL from beeing
    *        closed beofre the Approximate Execution Time has passed.
    *
    *        NOTE: The open connection mentioned above MAY not be closed after the Approximate Execution Time
    *              has passed, what is guranteed is that the connection will be closed after the
    *              Approximate Execution Time + the connection TTL (which is defined in the openConnectionGc)
    *              has passed.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static int executeUpdate(String sql,long approximateExecutionTime) throws SQLException{
        return internal_executeUpdate(sql,approximateExecutionTime);
    } //execute



    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    *
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    *
    * @param int maxRows- The maxRows limit is set to limit the number of rows that any ResultSet can contain. If the limit is exceeded, the excess rows are silently dropped.
    *
    *
    * @return Return type depends on the runInNewThread param either a ResultSet that contains the data produced by the query or a null;
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static ResultSet executeQuery(String sql,int maxSize) throws SQLException{
        return internal_executeQuery(sql,maxSize,0);
    } //execute



    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    *
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    *
    * @param int maxRows- The maxRows limit is set to limit the number of rows that any ResultSet can contain. If the limit is exceeded, the excess rows are silently dropped.
    *
    *
    * @param long  approximateExecutionTime - Approximate Execution Time in mSec for the supplied SQL,
    *        Use this param to prevent the open connection used to execute the supplied SQL from beeing
    *        closed beofre the Approximate Execution Time has passed.
    *
    *        NOTE: The open connection mentioned above MAY not be closed after the Approximate Execution Time
    *              has passed, what is guranteed is that the connection will be closed after the
    *              Approximate Execution Time + the connection TTL (which is defined in the openConnectionGc)
    *              has passed.
    *
    *
    *
    * @return Return type depends on the runInNewThread param either a ResultSet that contains the data produced by the query or a null;
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static ResultSet executeQuery(String sql,int maxSize,long approximateExecutionTime) throws SQLException{
        return internal_executeQuery(sql,maxSize,approximateExecutionTime);
    } //execute




    /**
    * Executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that
    * return nothing such as SQL DDL statements can be executed.
    *
    *
    * @param String  sql - an SQL INSERT, UPDATE or DELETE statement or an SQL statement
    *        that returns nothing
    *
    * @param boolean runInNewThread - this value determains whether the sql spesified be run in a A synchronicly in a new thread.
    *        sql runnig in a new thread will not return a ResultSet and will return a null value.
    *
    *
    * @param int maxRows- The maxRows limit is set to limit the number of rows that any ResultSet can contain. If the limit is exceeded, the excess rows are silently dropped.
    *
    *
    * @return Return type depends on the runInNewThread param either a ResultSet that contains the data produced by the query or a null;
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    private static ResultSet internal_executeQuery(String sql,int maxRows,long approximateExecutionTime) throws SQLException{

      ResultSet result        = null;
      Connection connection   = null;
      try {


          connection = getConnection();
          Statement statement = connection.createStatement();

          if (maxRows >0)
             statement.setMaxRows(maxRows );

          result = statement.executeQuery(sql);


          return result ;

      } //try
      catch (SQLException e){
            System.out.println("SQL Exception Error in SQLRunner.runSQL()");
            release(connection);
      throw e;
    }//catch

    } //internal_executeQuery



    private static int internal_executeUpdate(String sql,long approximateExecutionTime) throws SQLException{

        int result              = -1;
        Connection connection   = null;
          Statement statement     = null;

      try {

          connection = getConnection(approximateExecutionTime);
          statement = connection.createStatement();
          result = statement.executeUpdate(sql);


          return result ;

      } //try
      catch (SQLException ex) {

          System.out.println("SQL Exception Error in  SQLRunner.internal_executeUpdate()");
          release(statement.getConnection());

            throw ex;
      } //catch

    } //internal_executeUpdate



    private static void internal_executeUpdateInNewThread(String sql) throws SQLException{
              SQLExecutionQueue.getInstance().enqueue(sql);
    } //internal_executeUpdateInNewThread



    /**
    * This method will retrieve a new connection from the connection pool and return it.
    *
    * @return Connection the connection retrieved from the pool.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static Connection getConnection() throws SQLException{

        return internalGetConnection(true,0);

    } //getConnection


    /**
    * This method will retreive a new connection from the connection pool and return it.
    *
    *
    * @param Boolean bLogConnectioWithdrawal this var will state if we are running in debug mode,
    * if so a log entry will mark the fact that a connection was taken
    *
    * @return Connection the connection retreived from the pool.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static Connection getConnection(boolean bLogConnectioWithdrawal) throws SQLException{

        return internalGetConnection(bLogConnectioWithdrawal,0);

    } //getConnection (boolean isDebugMode)




    /**
    * This method will retreive a new connection from the connection pool and return it.
    * The approximateExecutionTime param will prevent the open connection used from beeing
    * closed beofre the Approximate Execution Time has passed.
    *
    *
    * @param long approximateExecutionTime - The approximateExecutionTime param will prevent the open connection used from beeing
    * closed beofre the Approximate Execution Time has passed.
    *
    *
    * @return Connection the connection retreived from the pool.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static Connection getConnection(long approximateExecutionTime) throws SQLException{

        return internalGetConnection(true,approximateExecutionTime);

    } //getConnection (long )





    /**
    * This method will retreive a new connection from the connection pool and return it.
    * The approximateExecutionTime param will prevent the open connection used from beeing
    * closed beofre the Approximate Execution Time has passed.
    *
    *
    * @param Boolean bLogConnectioWithdrawal this var will state if we are running in debig mode,
    * if so a log entry will mark the fact that a connection was taken
    *
    * @param long approximateExecutionTime - The approximateExecutionTime param will prevent the open connection used from beeing
    * closed beofre the Approximate Execution Time has passed.
    *
    *
    * @return Connection the connection retreived from the pool.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    *
    *
    */
    private static synchronized Connection internalGetConnection(boolean bLogConnectioWithdrawal,long approximateExecutionTime) throws SQLException{

        Connection result = null;


        try {

            if (dataSource == null)
                init();


            result =  dataSource.getConnection();


        }//try
      catch (Throwable ex){
            System.out.println("Warning, in SQLRunner. A corrupt connection was returned from the Connection pool");
        } //catch
    } //internalGetConnection



    /**
    * This method will release the connection stored inside the ResultSet object, thus returning it to the connection
    * pool.
    *
    * @param ResultSet rs the ResultSet which contains the connection to be released.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static void release(ResultSet rs) {

        if (rs == null)
            return;

        try {
            Statement statement= rs.getStatement();
            if (statement != null) {
                Connection tmpConn = statement.getConnection();

                rs.getStatement().close();

            } //if
        }//try
        catch (SQLException e) {
            System.out.println("Warning, Unable to release connection");
        } //catch
        catch (NullPointerException e) {
            System.out.println("Warning, Unable to release connection");
        } //catch

    } //release


    /**
    * This method will release the connection, thus returning it to the connection
    * pool.
    *
    * @param Connection connection the Connection to be released which was passed as a parameter.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static void release(Connection connection) {

        if (connection == null)
            return;

        try {
            connection.close();
        } //try
        catch (SQLException e) {
            System.out.println("Warning, Unable to release connection");
        } //catch
        catch (NullPointerException e) {
            System.out.println("Warning, Unable to release connection");
        } //catch

    } //release


    /**
    * This method will release the connection associated with the PreparedStatement object, thus returning it
    * to the connection pool.
    *
    *
    * @param PreparedStatement preparedStatement the PreparedStatement whose conenction is to be released.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
    public static void release (PreparedStatement preparedStatement ) {

        if(preparedStatement == null)
            return;

        try {
            Connection connection = preparedStatement.getConnection();
            preparedStatement.close();

            // return the connection autoCommit to the true before returning it to the connection pool
          if(!connection.getAutoCommit())
                connection.setAutoCommit(true);

            release(connection);
        } //try
        catch(SQLException e) {
            System.out.println("Warning, Unable to release PreparedStatement");
        }//catch
    }//release


    /**
    * This method will release the connection associated with the CallableStatement object, thus returning it
    * to the connection pool.
    *
    *
    * @param CallableStatement callableStatement the CallableStatement whose conenction is to be released.
    *
    *
    * Throws: SQLException
    *
    * if a database-access error occurs.
    *
    */
      public static void release (CallableStatement callableStatement) {
          if (callableStatement == null)
            return;

          try {
            callableStatement.close() ;
            release(callableStatement.getConnection());
          }//try
          catch (SQLException e) {
                System.out.println("Warning, Unable to release callableStatement");
          } //catch
      }//release


        /**
         * This method inits the connection pool
         * it gets the paramers from a properties file and init the data src
         * added by Amir Shevat
         */
    public static void init() throws SQLException , IOException {

      InputStream is = ClassLoader.getSystemResourceAsStream("db.properties");
      Properties dbProp = new Properties();
      try{
          dbProp.load(is);
      }//try
      catch(IOException io){
          System.out.println("could not find/load db.properties " + io.getMessage());
          throw io;
      }//catch

      // get poperties from prop file
      String strURL=dbProp.getProperty("db.url");
      String strUser=dbProp.getProperty("db.user");
      String strPassword=dbProp.getProperty("db.password");
      int MAX_NUMBER_OF_CONNECTIONS_IN_POOL = Integer.parseInt(dbProp.getProperty("db.MAX_NUMBER_OF_CONNECTIONS_IN_POOL"));
      int NUMBER_OF_PREOPENED_CONNECTIONS = Integer.parseInt(dbProp.getProperty("db.NUMBER_OF_PREOPENED_CONNECTIONS"));





      //stat data src
      OracleConnectionCacheImpl ods = null ;
      OracleConnectionPoolDataSource ocpds = null ;

      try{
          ocpds = new OracleConnectionPoolDataSource();
          ocpds.setURL(strURL);
          ods = new OracleConnectionCacheImpl(ocpds);
          ods.setMaxLimit (MAX_NUMBER_OF_CONNECTIONS_IN_POOL);
          ods.setUser(strUser);
          ods.setPassword(strPassword);
      }//try
          catch(SQLException sql){
          System.out.println("could not stat data src to DB " + sql.getMessage());
          throw sql;
      } //catch

      dataSource = ods;
      checkDS();
      initializeConnectionPool(NUMBER_OF_PREOPENED_CONNECTIONS);
      System.out.println("SQL connection pool was checked and filled");
    }//init


    /**
     * This method fills the pool with a num of connection
     * @param num the number of connections
     */
     private static void initializeConnectionPool(int num) throws  SQLException{

      System.out.println("LoaderServlet initializing connection pool");

      Connection connectionArr[] = new Connection[num];

      for (int nCounter = 0;nCounter<num;nCounter++)
            connectionArr[nCounter]=SQLRunner.getConnection();

      for (int nCounter = 0;nCounter<num;nCounter++)
            SQLRunner.release(connectionArr[nCounter]);
      connectionArr = null;

     }//initializeConnectionPool



    /**
     * This method checks a given DataSource
     * @param ds DataSource to test
     */
    static void checkDS() throws SQLException{

         Connection con = null;
         try{
           con =  dataSource.getConnection();
           Statement statement = con.createStatement();
           statement.executeQuery("select 'x' from dual");
         }//try
       catch(SQLException sql){
          System.out.println("SQL select 'x' from dual faild !!! at SQLRunner.checkDS()");
          throw sql;
         }//catch
       finally{
          try{
            con.close();
          }//try
          catch(Exception e){
              System.out.println("connection close faild !!! at SQLRunner.checkDS()");
            e.printStackTrace();
          }//catch
         }// finally
    }//checkDS


    /**
     * This method will reset the datasource variable thus causing the SQLRunner to
     * initialize itself upon any mehtod call it receives.
     */
    static void resetDataSource(){
      dataSource = null;
    }//resetDataSource

} //SQLRunner


Hope this helps
0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 19

Expert Comment

by:Jim Cakalic
ID: 7012185
This article at JavaWorld (sorry for being late but the site was down for a few days so I couldn't find the link) describes a solution quite similar to that which you describe. The intent of the "DebuggableStatement" is to assist in debugging. But I think you could take the idea and the presented code and use it for audit logging or other similar purposes.

    http://www.javaworld.com/javaworld/jw-01-2002/jw-0125-overpower_p.html

Best regards,
Jim Cakalic
0
 

Author Comment

by:kwang080897
ID: 7015725
thanks !!!

0
 

Author Comment

by:kwang080897
ID: 7015730
hi jim !.......

thanks.... i will give you some points as well

0

Featured Post

Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

This was posted to the Netbeans forum a Feb, 2010 and I also sent it to Verisign. Who didn't help much in my struggles to get my application signed. ------------------------- Start The idea here is to target your cell phones with the correct…
Introduction This article is the first of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article explains our test automation goals. Then rationale is given for the tools we use to a…
This tutorial covers a practical example of lazy loading technique and early loading technique in a Singleton Design Pattern.
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now