Sunday, March 15, 2009

AJAX Paging Internals

PagingCalc java class described in Paging Internals could be used in usual and AJAX web applications.

This AJAX Paging Demo web project is based on MVC model 1.
Specifications: Servlet 2.5. Libraries: json-lib-2.2.3.
It could be tested with Tomcat 6.0.x and it could be built with Maven 2.

Check the code below:
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(true);

PagingBean bean = getPagingBean(request);
JSONObject jsonObject = JSONObject.fromObject(bean);

response.setHeader("Content-type", "text/json");
response.getOutputStream().print(jsonObject.toString());
}

private PagingBean getPagingBean(HttpServletRequest request){
int currPage = getCurrPage(request);

PagingCalc calc = new PagingCalc(PagingCalc.DEFAULT_ITEMS_COUNT,
PagingCalc.DEFAULT_INDEXES_COUNT, currPage, dataIF.getDataRowsCount());

PagingBean bean = new PagingBean();
bean.setData(dataIF.getDataRows(calc.getStartItem(), calc.getEndItem()));
bean.setActionUrl("json");
bean.setCurrPageParam(PagingBean.DEFAULT_CURR_PAGE_PARAM);
bean.setCalc(calc);

return bean;
}

Servlet returns JSON like next:
{"actionUrl":"json",
"calc":{"currPage":2,
"endItem":6,
"endPage":5,
"firstLink":1,
"firstLinkVisible":true,
"lastLink":8,
"lastLinkVisible":true,
"nextLink":6,
"nextLinkVisible":true,
"pagesCount":8,
"prevLink":0,
"prevLinkVisible":false,
"startItem":4,
"startPage":1,
"totalItems":23},
"currPageParam":"currPage",
"data":[{"description":"description 4","id":"4","name":"name 4"},
{"description":"description 5","id":"5","name":"name 5"},
{"description":"description 6","id":"6","name":"name 6"}]}

Download project binary and sources.

Saturday, March 14, 2009

Exec with stdout / output redirected

You could have exec feature in your code / application. But if you need to exec with output redirected to a log file it will not work without pipes operations. But there is one trick - exec bash and provide your command with output redirected to a log as parameter to bash.

Syntax:
bash -c "command"

Example:
bash -c "ls -la"

Example with stdout redirected to a log:
bash -c "ls -la > log.txt"

In programming languages usually we have exec with several parameters:
  • path - string
  • args - array

java: Runtime.exec
php: pcntl_exec

So we need to prepare something like next:
  • path = /bin/bash
  • args:
    • arg1 = -c
    • arg2 = "command > log"     or     "command > log 2>&1"

Refer the PHP code below:
<?php
/**
* Execute an external program
* @param string $path path to external program
* @param array $args program arguments
*/

function execute($path, $args) {
return pcntl_exec($path, $args);
}

/**
* Execute an external program with output redirected to log
* @param string $path path to external program
* @param array $args program arguments
* @param string $log log file name
*/

function execute_redir($path, $args, $log) {
$path .= ' '.implode(' ', $args);

$args = array();
$args[] = '-c';
$args[] = $path.' > '.$log.' 2>&1';

$path = '/bin/bash';
execute($path, $args);
}

// execute without output redirected
//execute('/bin/ls', array('-la'));

// execute with output redirected
execute_redir('/bin/ls', array('-la'), 'log.txt');
?>

Tuesday, March 10, 2009

Paging Internals

If you have a task to create custom paging by yourself then this post could be helpful for you.

This Paging Demo web project is based on MVC model 1.
Specifications: Servlet 2.5 / JSP 2.1 / JSTL 1.2.
It could be tested with Tomcat 6.0.x and it could be built with Maven 2.

Download project binary and sources.

Paging design:

Class diagram:

Screens: This paging is based on a java class PagingCalc (paging calculator). It could do most calculations required by paging. The class follows Immutable Design Pattern. Check the code below:

package com.blogspot.illyakeeplearning.paging;

/**
* Paging Calculator
*/

public class PagingCalc {
public static final int DEFAULT_ITEMS_COUNT = 3;
public static final int DEFAULT_INDEXES_COUNT = 5;

private int maxItemsCount;
private int maxIndexesCount;

private int currPage;
private int totalItems;

private int pagesCount;
private int startPage;
private int endPage;

/**
* Constructor
*
* @param maxItemsCount max items count on page
* @param maxIndexesCount max page indexes count in a page block
* @param currPage current page index
* @param totalItems total items count
*/

public PagingCalc(int maxItemsCount, int maxIndexesCount, int currPage, int totalItems) {
this.maxItemsCount = maxItemsCount;
this.maxIndexesCount = maxIndexesCount;
this.currPage = currPage;
this.totalItems = totalItems;

initInternalVariables();
}

/**
* Inits internal variables
*/

private void initInternalVariables() {
initPagesCount();
initStartPage();
initEndPage();
}

/**
* Inits pages count
*/

private void initPagesCount() {
pagesCount = totalItems / maxItemsCount;
pagesCount += (totalItems % maxItemsCount == 0) ? 0 : 1;
}

/**
* Inits start page
*/

private void initStartPage() {
startPage = (currPage + maxIndexesCount - 1) / maxIndexesCount;
startPage = (startPage - 1) * maxIndexesCount + 1;
}

/**
* Inits end page
*/

public void initEndPage() {
endPage = startPage + maxIndexesCount - 1;
endPage = (endPage > pagesCount) ? pagesCount : endPage;
}

/**
* Gets current page index. 1-based value
*
* @return current page index. 1-based value
*/

public int getCurrPage() {
return currPage;
}

/**
* Returns total items count
*
* @return total items count
*/

public int getTotalItems() {
return totalItems;
}

/**
* Gets start item index on page
*
* @return start item index on page
*/

public int getStartItem() {
int startItem = (currPage - 1) * maxItemsCount + 1;
return startItem;
}

/**
* Gets end item index on page
*
* @return end item index on page
*/

public int getEndItem() {
int endItem = currPage * maxItemsCount;
endItem = (endItem > totalItems) ? totalItems : endItem;
return endItem;
}

/**
* Gets pages count
*
* @return pages count
*/

public int getPagesCount() {
return pagesCount;
}

/**
* Gets start page
*
* @return start page
*/

public int getStartPage() {
return startPage;
}

/**
* Gets end page
*
* @return end page
*/

public int getEndPage() {
return endPage;
}

/**
* Is Prev Link Visible
*
* @return true, if Prev Link Visible
*/

public boolean isPrevLinkVisible() {
return (pagesCount > maxIndexesCount) && (startPage > 1);
}

/**
* Gets Prev Link
*
* @return prev link
*/

public int getPrevLink() {
return startPage - 1;
}

/**
* Is Next Link Visible
*
* @return true, if Next Link Visible
*/

public boolean isNextLinkVisible() {
return (pagesCount > maxIndexesCount) && (endPage < pagesCount);
}

/**
* Gets Next Link
*
* @return next link
*/

public int getNextLink() {
return endPage + 1;
}

/**
* Is First Link Visible
*
* @return true, if First Link Visible
*/

public boolean isFirstLinkVisible() {
return currPage != 1;
}

/**
* Gets First Link
*
* @return first link
*/

public int getFirstLink() {
return 1;
}

/**
* Is Last Link Visible
*
* @return true, if Last Link Visible
*/

public boolean isLastLinkVisible() {
return (currPage != pagesCount);
}

/**
* Gets Last Link
*
* @return last link
*/

public int getLastLink() {
return pagesCount;
}
}

PagingCalc could be initialized...
PagingCalc calc = new PagingCalc(maxItemsCount, maxIndexesCount, currPage, totalItems);

...and be used
calc.getStartPage();
calc.getEndPage();

Download project binary and sources.

Saturday, March 7, 2009

Raise Exception / Error in MySQL 5.0

There is no standard way to raise exception or error in MySQL.
But there is a trick:
  • call to a not existing Stored Procedure raises MySQL exception
  • so call to not existing Stored Procedure using "error message" as "procedure name" forces MySQL to raise exception that looks like "error message"
So to have Raise Exception feature we need next stored procedures / functions:
  • procedure to execute dynamic SQL statements
  • function to format text
  • and main procedure to raise error / exception

See the code below:
DELIMITER $$


-- PROCEDURE pExecuteImmediate
-- executes dynamic SQL statement
-- Params:
-- tSQLStmt - SQL statement to be executed
DROP PROCEDURE IF EXISTS `pExecuteImmediate` $$
CREATE PROCEDURE `pExecuteImmediate`(IN tSQLStmt TEXT)
BEGIN
SET @executeImmediateSQL = tSQLStmt;
PREPARE executeImmediateSTML FROM @executeImmediateSQL;
EXECUTE executeImmediateSTML;
DEALLOCATE PREPARE executeImmediateSTML;
END $$


-- FUNCTION fFormat
-- replaces '%s' in a string with some value
-- Params:
-- sFormat - string to be formatted
-- sPar1 - value used in replacement
-- Returns:
-- formatted string
DROP FUNCTION IF EXISTS `fFormat` $$
CREATE FUNCTION fFormat(sFormat TEXT, sPar1 TEXT)
RETURNS TEXT
BEGIN
RETURN REPLACE(sFormat, '%s', sPar1);
END $$


-- PROCEDURE pRaiseError
-- raises error
-- Params:
-- sError - error message
DROP PROCEDURE IF EXISTS `pRaiseError` $$
CREATE PROCEDURE `pRaiseError`(sError VARCHAR(255))
BEGIN
-- trick
-- calling of not existing procedure with name that equals error message
-- will force MySQL exception that looks like error message
CALL pExecuteImmediate(fFormat('CALL `error: %s, solution`', sError));
END $$


DELIMITER ;

After execution of SQL:
CALL pRaiseError("this is a sample error message")

MySQL will raise exception:
PROCEDURE error: this is a sample error message, solution does not exist

Additional information could be found here: Stored Procedures in MySQL 5.x

Wednesday, March 4, 2009

Disable Triggers in MySQL 5.0

Information about triggers in MySQL 5.0 you can find here:
It is not possible to temporary disable triggers in MySQL 5.0.
But there are several tricks:
  1. drop/create triggers
    • drop triggers
    • do anything you need (import, update, etc)
    • create triggers

  2. Use global variable
    • each trigger should check this variable in its code beginning
    • set global variable to 1 (or other NOT NULL value) to disable triggers
    • do anything you need (import, update, etc)
    • set global variable to NULL to enable triggers
    • global variable IS NULL by default

    • see sample below:

  3. schema:

    script:

    CREATE TABLE `users` (
    `id` int(10) unsigned NOT NULL auto_increment,
    `name` varchar(45) NOT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    CREATE TABLE `properties` (
    `userid` int(10) unsigned NOT NULL,
    `updated` varchar(45) NOT NULL,
    PRIMARY KEY (`userid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    trigger:
    DROP TRIGGER IF EXISTS tUsersInsert;

    DELIMITER $$
    CREATE TRIGGER tUsersInsert AFTER INSERT ON users
    FOR EACH ROW BEGIN
    IF (@DISABLE_TRIGGERS IS NULL) then
    # main trigger part
    INSERT INTO properties VALUES(NEW.id, NEW.name);
    END IF;
    END; $$
    DELIMITER ;

    trigger is enabled during insert:

    INSERT INTO users(name) VALUES('aaa');

    disable trigger during insert:
    BEGIN;
    SET @DISABLE_TRIGGERS=1;
    INSERT INTO users(name) VALUES('bbb');
    SET @DISABLE_TRIGGERS=NULL;
    COMMIT;

    result:

Sunday, March 1, 2009

Ant tips

  1. JWSDP integration
    <path id="jaxrpc.libs">
    <fileset dir="${lib.dir}/jwsdp-1.5/jaxrpc">
    <include name="*.jar" />
    </fileset>
    </path>

    <path id="saaj.libs">
    <fileset dir="${lib.dir}/jwsdp-1.5/saaj">
    <include name="*.jar" />
    </fileset>
    </path>

    <path id="jwsdp-shared.libs">
    <fileset dir="${lib.dir}/jwsdp-1.5/jwsdp-shared">
    <include name="*.jar" />
    </fileset>
    </path>

    <path id="tools.jar">
    <pathelement location="${env.JAVA_HOME}/lib/tools.jar" />
    </path>

    <taskdef name="wscompile" classname="com.sun.xml.rpc.tools.ant.Wscompile">
    <classpath>
    <path refid="jaxrpc.libs" />
    </classpath>
    </taskdef>

    <target name="generate-wsclient" depends="create-target-dirs">
    <wscompile keep="true" fork="true" client="true" features="noencodedtypes" base="${classes.dir}" sourceBase="${gensrc.dir}" mapping="${classes.dir}/ProvisionService-mapping.xml" nonClassDir="${config.dir}" xPrintStackTrace="true" verbose="true" config="${config.dir}/ProvisionService-client-config.xml">
    <classpath>
    <path refid="jaxrpc.libs" />
    <path refid="jwsdp-shared.libs" />
    <path refid="tools.jar" />
    </classpath>
    </wscompile>
    </target>

    <target name="generate-ws-test" depends="create-target-dirs">
    <wscompile keep="true" fork="true" server="true" features="noencodedtypes" base="${test-classes.dir}" sourceBase="${test-gensrc.dir}" mapping="${test-classes.dir}/ProvisionService-mapping.xml" nonClassDir="${config.dir}" xPrintStackTrace="true" verbose="true" config="${config.dir}/ProvisionService-config.xml">
    <classpath>
    <path refid="jaxrpc.libs" />
    <path refid="saaj.libs" />
    <path refid="jwsdp-shared.libs" />
    <path refid="tools.jar" />
    </classpath>
    </wscompile>
    </target>

  2. JUnit integration
    <path id="junit.libs">
    <fileset dir="${lib.dir}/junit-3.8.1">
    <include name="**/*.jar" />
    </fileset>
    </path>

    <junit printsummary="yes" haltonfailure="no" fork="true">
    <classpath>
    <path refid="junit.libs" />

    <pathelement path="${classes.dir}" />
    <pathelement path="${test-classes.dir}" />
    <pathelement path="${test-resources.dir}" />
    </classpath>

    <formatter type="plain" />

    <batchtest todir="${test-results.dir}">
    <fileset dir="${test-classes.dir}">
    <include name="**/ws/client/test/unit/**" />
    </fileset>
    </batchtest>
    </junit>

  3. JMeter integration
    <path id="jmeter-ant.libs">
    <fileset dir="${lib.dir}/jakarta-jmeter-2.2/extras">
    <include name="ant-jmeter.jar" />
    </fileset>
    </path>

    <taskdef name="jmeter" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask">
    <classpath>
    <path refid="jmeter-ant.libs" />
    </classpath>
    </taskdef>

    <jmeter jmeterhome="${lib.dir}/jakarta-jmeter-2.2" testplan="${test-results.dir}/WebService.jmx" resultlog="${test-results.dir}/WebService.jtl" />
    <xslt in="${test-results.dir}/WebService.jtl" out="${test-results.dir}/WebService.html" style="${lib.dir}/jakarta-jmeter-2.2/extras/jmeter-results-report_21.xsl" />

  4. AntContrib integration
    <taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
    <pathelement location="${libs.dir}/ant/ant-contrib-1.0b3.jar"/>
    </classpath>
    </taskdef>

    <target name="dist-sql">
    <!-- iterate over each .sql file found within database folder
    and call concatSQLFile target for them: -->
    <foreach target="concatSQLFile" param="targetFile">
    <path>
    <fileset dir="database">
    <include name="*.sql"/>
    </fileset>
    </path>
    </foreach>
    </target>

    <!-- is called by target dist-sql
    param: targetFile - the sql file to append -->
    <target name="concatSQLFile">
    <basename property="targetFileShort" file="${targetFile}"/>
    <echo message="${targetFileShort}"/>

    <concat destfile="${dist.dir}/${sql.name}" append="true">
    <header>-- ${targetFileShort}${line.separator}</header>
    <fileset file="${targetFile}"/>
    <footer>-- ${line.separator}</footer>
    </concat>
    </target>

  5. SSH integration
    <sshexec host="${host}" username="${user}" password="${password}" command="rm -rf ${path}/${subfolder}; mkdir -p ${path}/${subfolder}"/>

    <scp todir="${user}:${password}@${host}:${path}/${subfolder}">
    <fileset dir="${dist.dir}">
    <include name="${release.archive}"/>
    <include name="${docs.archive}"/>
    </fileset>
    </scp>