Thursday, February 17, 2011

Integrate Mantis+Subversion on Windows

Integrate Mantis+Subversion on Windows

3 days ago I got a task to integrate mantis with subversion, so that each commit will be linked with a mantis' issue. I
am a java guy, and also know some about python(anyway I every learned python and adopted it in my project). Mantis is
developed by PHP, as I have some experience with script language, it is not a seemingly hard task for me.

System:
Apache 2.2
Mantis 1.2.4
Mysql 5.0
PHP 5.3.5 (zip archieve)
Python 2.6.6
VisualSVN 2.1.5
Window XP(32bit)

1. Install PHP
* download PHP from http://www.php.net/
* upzip to d:/php-5.3.5(you can unzip to any your favourite directory)
* configure PHP.
* Check to see if you already have a PHP configuration file or not: d:\php-5.3.5\php.ini.
* If not copy d:\php-5.3.5\php.ini-production to d:\php-5.3.5\php.ini
* Open this file php.ini with "notepad", uncomment '; extension_dir = "ext"'
NOTE: maybe you should set extension_dir to absolute path d:/php-5.3.5/ext, as when I set it to 'ext', a strange
window exception 'memory is only read' or something like that will be thrown out when try to launch apache.
* set upload_max_filesize = 8M, you should make corresponding adjustment in mantis later.
* load mysql module, uncomment this line ';extension=php_mysql.dll'
* set timezone, or mantis will show some warning message 'date.timezone = Asia/Shanghai'

2. Install Python
* download python from www.python.org, the version must be 2.6.X(I will explain why only 2.6.x later)
* install python to d:/python26 (you can install to you favourite directory)
* add python to window path environment vairable

3. Install Mysql
* download mysql from www.mysql.com
* install mysql to d:/mysql/mysql-server-5.0(you can install to you favourite directory). When installation, you'd
better install the development kit also(c headers).
* create 'root' user with password 'root'

4. Install Apache
* download apache from www.apache.org
* install apache to d:/apache2.2(you can install to you favourite directory)
* configuring Apache to Use PHP as a Loaded Module
* Use "notepad" to open d:\Apache2.2\conf\httpd.conf
* Go to the LoadModule section and enter:
LoadModule php5_module "d:/php-5.3.5/php5apache2_2.dll"
PHPIniDir "d:/php-5.3.5"
* Go to mime_module section and define a mime type as:

...
AddType application/x-httpd-php .php

NOTE: refer to http://windows.fyicenter.com/71_Apache_PHP_Tutorials_Installing_and_Running_Apache_Server_and.html

5. Setup Mantis in Apache
* unzip mantis and copy to d:\apache2.2\htdocs, let's say it is D:\Apache2.2\htdocs\mantisbt
* start apache server, and access 'http://localhost/mantisbt/admin/install.php'
* follow the instruction to configure mantis, more detail pls refer to http://www.mantisbt.org/
* configure email and more. below is my config_inc.php(if no-exist, create one) which can be found in D:\Apache2.2\htdocs\mantisbt.

#----------------------------------------#
# DATABASE #
#----------------------------------------#
$g_hostname = 'localhost';
$g_db_type = 'mysql';
$g_database_name = 'mantisbt';
$g_db_username = 'root';
$g_db_password = '111111';

#----------------------------------------#
# INTERNATIONALIZATION #
#----------------------------------------#
#internationalize...chinese-simple UI
#$g_default_language = 'auto';
$g_default_language = 'english';

#----------------------------------------#
# FILE UPLOADING #
#----------------------------------------#
# must create a 'upload' folder in mantis, and set the upload folder as 'upload' when create project.
$g_file_upload_method = DISK;
# the max file size must be less than php configuration 8M
$g_max_file_size = 7000000;

#----------------------------------------#
# LOG #
#----------------------------------------#
# looks like it doesn't work
$g_log_destination = 'file:e:/tmp/mantis.log';

#----------------------------------------#
# EMAIL #
#----------------------------------------#
#$g_administrator_email = 'ramon.li@mpos.net';
#$g_webmaster_email = 'ramon.li@mpos.net';
# the sender email, part of 'From: ' header in emails
#$g_from_email = 'mantis@mpos.net';
# the sender name, part of 'From: ' header in emails
$g_from_name = 'Mantis Bug Tracker';
# the return address for bounced mail
#$g_return_path_email = 'mantis@mpos.net';
# allow email notification
# note that if this is disabled, sign-up and password reset messages will
# not be sent.
$g_enable_email_notification = ON;
$g_phpMailer_method = PHPMAILER_METHOD_SMTP;
$g_smtp_host = 'mail.mpos.net';
$g_smtp_port = 25;
$g_smtp_connection_mode = '';
$g_smtp_username = 'ramon.li@mpos.net';
$g_smtp_password = 'ramonramon';

?>
* create new user of mantis...
* create new project ...

6. Config MysqlDB for python
* download binary package of mysql-python from http://www.technicalbard.com/files/MySQL-python-1.2.2.win32-py2.6.exe
* install mysql-python
NOTE: I ever tried to compile source package from http://mysql-python.sourceforge.net/, but it is really hard. In
order to setup a gcc environment, I tried cygwin, mingw32, even visualC++, but what I got are all failure.

7. Install VisualSVN
* download VisualSVN server from http://www.visualsvn.com/
* create a new repository at "D:\project\VisualSVN-Server\Repositories\HelloWorld"

8. Integrate Subversion with Mantis
* as on window, create pre-commit.bat at "D:\project\VisualSVN-Server\Repositories\HelloWorld\hooks" first.
@echo off
set path=%path%;"D:\SVN_SERVER\svn-win32-1.5.4\bin"
setlocal
set REPOS=%1
set TXN=%2

python D:\project\VisualSVN-Server\Repositories\HelloWorld\hooks\mantis_integration.py %REPOS% %TXN%

if %errorlevel% gtr 0 goto err
exit 0
:err
rem echo Empty log message not allowed. Commit aborted! 1>&2
exit 1
* implement python script mantis_integration.py.
"""
This script will integrate subversion with mantis, its general purpose is to verify that each commit of subversion must
be linked with a open bug of mantis.
"""
import sys,os,datetime,time
import logging
import re
import _mysql as mysql

# init logging
LOG_FILENAME = 'e:/tmp/mantis_subversion.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.ERROR)

# database setting
host = '192.168.2.9'
username = 'root'
password = 'root'
database = 'mantisbt'

# mantis setting
mantis_user_id = 3 #user:'svn'

def integrate_mantis(rep_path, txn):
"""
update the log message and changed list of subversion to a mantis issue, the general work flow as below:
1. check if the log message follow a spcified pattern, if true, start to check mantis, otherwise rollback the transaction.
2. check if the issuse specified in log message exists in mantis, if false, rollback the transaction.
3. insert log message and changed list to mantis.
"""
try:
# the format of log message must follow '#issuenumber log'
log = svnlook('log', rep_path, txn).strip()
logging.debug('log=' + log)
issue_number = get_issue_number(log)
# extract the log message
log = log[(log.find(' ')+1):].strip()

author = svnlook('author', rep_path, txn).strip()
logging.debug("author=" + author)
changed = svnlook('changed', rep_path, txn).strip()
logging.debug("changed=" + changed)

#check if the issue exists in mantis
conn = mysql.connect(host, username, password, database)
# set charset, python will use UTF-8 as default
conn.set_character_set('GBK')
if is_issue_exist(conn, issue_number):
update_mantis(conn, issue_number, author, log, changed)
except Exception as e:
logging.error(e)
sys.exit(1)

def update_mantis(conn, issue_number, author, log, changed):
"""
insert a bug note to specifed issue in mantis.
"""
note = author + "@" + datetime.datetime.now().ctime() + "
" + log + "

" + changed
query = "insert into mantis_bugnote_text_table(note) values('" + note + "')";
conn.query(query)
bugnote_text_id = conn.insert_id()

now = str(int(time.time()))
query = "insert into mantis_bugnote_table(bug_id,reporter_id,bugnote_text_id,time_tracking,last_modified,date_submitted) "
query += "values(" + issue_number + "," + str(mantis_user_id) + "," + str(bugnote_text_id) + ",0," + now + "," + now + ")"
conn.query(query)

def is_issue_exist(conn, issue_number):
"""
does the issue with a specified number exists in mantis??
"""
query = "select * from mantis_bug_table where id=" + issue_number
conn.query(query)
r = conn.store_result()
if r.num_rows() <= 0:
msg = "No issue(id=" + issue_number + ") exist in mantis"
sys.stderr.write(msg)
raise Exception(msg)
return True

def get_issue_number(log):
log_pattern = r'^[#]{1}(\d+)[\s]+(.+)'
result = re.search(log_pattern, log)
if result == None:
msg = 'the log message must follow: #issue_number log_message'
sys.stderr.write(msg)
raise Exception(msg)
return result.groups()[0]

def svnlook(subcommand, rep_path, txn):
output = os.popen('svnlook ' + subcommand + ' ' + rep_path + ' -t ' + txn)
#output = os.popen('svnlook ' + subcommand + ' ' + rep_path)
return output.read()

if __name__ == "__main__":
if len(sys.argv) != 3:
sys.stderr.write("[Usage] python " + sys.argv[0] + " $REP_PATH $TXN, pls check if you have provided all need arguments.");
sys.exit(1);

integrate_mantis(sys.argv[1], sys.argv[2]);

Thursday, February 10, 2011

Inheritence and Constructor

If a class has no constructor, javac will add a default no parameter constructor to it automatically when compile, and all
classes are derived from Object which has a default blank constructor.
When call the constructor of a class, it will call constructor of superclass first, except that it explicitly calls another
constructor using this(...) or super(...).
Let's run some sample code.
class A{
private int index = 11;
public A(){
System.out.println("A()");
}

public A(String s){
System.out.println("A(String)");
}

public int getIndex(){
return index;
}
}

class B extends A{
private String name;
public B(){
// this("ramon");
System.out.println("B()");
}

public B(String name){
// super("ramon");
System.out.println("B(String)");
}
}

// check the test case
public class Main{

public static void main(String args[]){
B b = new B();
System.out.println("---------------");
B b1 = new B("ramon");
}
}

The output is:
A()
B()
---------------
A()
B(String)
You can see that every constructor of B will call A() first, now we apply a little change.
class A{
private int index = 11;
public A(){
System.out.println("A()");
}

public A(String s){
System.out.println("A(String)");
}

public int getIndex(){
return index;
}
}

class B extends A{
private String name;
public B(){
this("ramon");
System.out.println("B()");
}

public B(String name){
// super("ramon");
System.out.println("B(String)");
}
}

// check the test case
public class Main{

public static void main(String args[]){
B b = new B();
System.out.println("---------------");
B b1 = new B("ramon");
}
}
The output is :
A()
B(String)
B()
---------------
A()
B(String)
Due to this("ramon") in B(), it will call B(String name) first, but B(String name) also need to call A() first(call default constructor
of superclass), finally the output is:
A()
B(String)
B()

Apply some more change:
class A{
private int index = 11;
public A(){
System.out.println("A()");
}

public A(String s){
System.out.println("A(String)");
}

public int getIndex(){
return index;
}
}

class B extends A{
private String name;
public B(){
this("ramon");
System.out.println("B()");
}

public B(String name){
super("ramon");
System.out.println("B(String)");
}
}

// check the test case
public class Main{

public static void main(String args[]){
B b = new B();
System.out.println("---------------");
B b1 = new B("ramon");
}
}
The output is:
A(String)
B(String)
B()
---------------
A(String)
B(String)
This time B() will call B(String name) first, but B(String name) will call A(String name) first(due to super("ramon")).
From the example above, you should know the relationship between constructor and inheritence.

Wednesday, February 09, 2011

Go deep into servlet3

Deep into Servlet3

Servlet is the most widely used technology in j2ee platform, many developers know it, but not really understand it. In this article I would
like to share some more detailed understanding of it.

Response and socket connection.
----------------------------------
The response object encapsultes all information to be returned from the server to the client. A socket connection is a physical connection
between client and server. In HTTP 1.0, each reqeust/response must establish a new connection, when servelt return the response to the client,
the connection will be closed wa well. But HTTP 1.1 introduces persistent connection, in this case, when servlet return the response to
the client, the connection will not be closed, the client can send a new request on this connection until either side closes the connection
explicitly. Anyway we must know that resposne is'nt a abstract of connection in the servlet context.

- Response.flush(): will force the content in the buffer to be written to the client, the response will be considered as committed.
- Response.reset()/resetBuffer(): the reset() method clears the data in buffer when the response isn't committed. Headers and status code
set by the servlet prior to the reset call must be cleared as well. resetBuffer() will only reset the data in buffer.
- Response.setBufferSize(): The servlet can request a preferred buffer size, but the buffer assigned is not required to be the sieze requested
by the servlet, but must be at least as large as the size requested.

Committed??
----------------------------------
The isCommitted() method return a boolean value indicating whether any response bytes have been returned to client. A servlet container is
allowed, but not required, to buffer output going to the client for efficiency purpose.When using a buffer, the container must immediately
flush the contents of a filled buffer to the client. If this is the first data that is sent to client, the response is considered as committed.

To be successfully transmitted back to the client, headers must be set before the response is committed. Headers set after the response is
committed will be ignored by the servlet container.
Below methods will make the response to be committed:
- Response.flushBuffer()
- Response.getWriter().flush();
- Response.sendError(): The sendErro() method will set the appropriate headers and content body for an erro message to return to the client.
- Response.sendRedirect(): The sendRedirect() method will set the appropriate headers and content body to redirect the client to a different URL.
NOTE: sendErro() and sendRedirect() will have the side effect of committing the response, if it has not already committed,and terminating it.
No further outpu to the client should be made by the servlet after these methods are called. If data is written to the response after these
methods are called, the data will be ignored.

Closure??
----------------------------------
When a response is closed,the container must immediately flush all remaining content in the response buffer to the client. the following events
indicate that the servlet has satisfied the request and that the response object i to be closed:
- The termination of the service() method os the servlet.
- The amount of content specified in the setContentLength() method of the response has been greater than zero and has been written to the response.
- The sendError() method is called.
- The sendRedirect() method is called.
- The complete() method on AsyncContext is called.

Each response object is valid only within the scope of a servlet's service() method, or within the scope of a filter's doFilter() method, unless
the associated request object has asynchronous processing enabled for the component. If asynchronous processing on the associated reqeust is started,
then the reqeust object remains valid until complete() method on AsyncContext is called. Containers commonly recycle response objects in order to
avoid the performance overhead of response object creation.

Dispatcher forward/include.
----------------------------------
The Container Provider should ensure that the dispatch of the request to a target servlet occurs in the same thread of the same JVM as the original
request.
- RequestDispatcher.include(): It can ben called at any time. The target servlet of the include method has access to all aspects of the request
object, but its use of the response object is more limited.
It can only write information to the ServletOuputStream or Writer of the response object and commit a response by writing content past the end of
the response buffer, or by ecplicitly calling the flushBuffer() method of the ServletResponse interface. It cannot set headers or call any method
that affects the ehaders of the response.
When return from target servlet, you can return content in the buffer to the client if the response object isn't closed, or even set headers if the
response object isnot committed.
- RequestDispatcher.forward(): This method may be called by the calling servlet only when no output has been committed to the client. If outpu data
exists in the response buffer that has not been committed, the content must be cleared before the target servlet's service() method is called. If
the response has been committed, all IllegalStateException must be thrown.
When return from the target servlet, the response object is closed, you cannot apply any modification to it any more.

Asynchronous processing.
----------------------------------
Refer to http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-02-2009/jw-02-servlet3.html&pagename=/javaworld/jw-02-2009/jw-02-servlet3.html&pageurl=http://www.javaworld.com/javaworld/jw-02-2009/jw-02-servlet3.html&site=jw_core
But when check the sourcecode of class SlowWebService which is a ServletContextListener, I found that there is a infinite loop in the contextInitiate()
method, that means when container try to lunch a web application, it will never be successful. I really don't understand why put a infinite loop block
there, my tests in jetty and tomcat both demonstrate that container will fall into the infinite loop and cannot go forward to finish the launch and cannot
be ready to serve any request from the client.