Friday, January 9, 2015

Maven Jetty PermGen space

before run "mvn jetty:run"

run this command in window env

set MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=512m

or unix-like env
export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=512m"

Friday, January 2, 2015

Spring MVC read properties in the class

This is sample code:

@Component( "emailService" )
public class EmailService {

  @Value( "${mail.server.url}" )
  private String url;

....
}

in the property file
mail.server.ur=http://abc/def


Monday, December 29, 2014

Android dev study notes - 1


  • Dailer
------ code snippet ------
       String number = tv.getText().toString();  // get No from an edittext
    Intent it = new Intent(Intent.ACTION_DIAL);
    it.setData(Uri.parse("tel:"+number));
    this.startActivity(it);

------- permission ------
<uses-permission android:name="android.permission.CALL_PHONE"/>
  • SMS
------ code snippet ------
        String mNo = evMobileNo.getText().toString();
String content = evSMSContent.getText().toString();
SmsManager sm = SmsManager.getDefault();
ArrayList<String> text = sm.divideMessage(content);  // just in case message is too long
for (String s : text) {
sm.sendTextMessage(mNo, null, s, null, null);
}
Toast.makeText(MainActivity.this, "sent success",
Toast.LENGTH_LONG).show();

------- permission ------
 <uses-permission android:name="android.permission.SEND_SMS" />
  • Save/Read File to/from memory card

public void savePrivateMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_PRIVATE);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_PRIVATE, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be overwrited.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public void saveAppendMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_APPEND);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_APPEND, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be appended.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public String read(String fileName) throws Exception {
FileInputStream input = context.openFileInput(fileName);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

This method is allowed to read file under  /data/data/<app package name>/files/<file name> at the same app.

If another app needs to access this file in this app, the code is as follows,

public String read(String fileName) throws Exception {
                String fn = "/data/data/<package name>/files/"+fileName;
                File file = new File(fn);
                // should NOT be  context.openFileInput(fileName); it is for memory card
FileInputStream input = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

If the mode is Context.MODE_PRIVATE or Context.MODE_APPEND, cannot read file from external app, unless the external app set the mode as Context.MODE_WORLD_READABLE when saving the file.
If an app is open the file to be read/write for external apps, when save a file, the file mode need to set as Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE


  • Save/Read File to/from SD card
before read/write file to/from SD card, need to check the card is available or not by the following,

if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))

file location:
File file = new File(Environment.getExternalStorageDirectory(),fileName);

Enviroment.getExternalStorageDirectory() points to /mnt/sdcard for current android os, and points to /sdcard for very older os version.
  • SharedPreferences
let's say user chooses preferred color and font size for UI setting, then save such info to SharedPreferences.

public void save(String color, int fontSize) {
SharedPreferences sf = context.getSharedPreferences("custprefer",
Context.MODE_PRIVATE);
Editor edit = sf.edit();
edit.putString("color", color);
edit.putInt("fontSize", fontSize);
edit.commit();
}

in the end, a new file custprefer.xml will be generated at folder /data/data/<package>/shared_prefs/custprefer.xml  as follows,

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="fontSize" value="12" />
<string name="color">red</string>
</map>

  • SQLite
package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

From the constructor, set db version is 1, and db file name is demo.db, onCreate() will be triggered if demo.db does not exit.

This is a test class to trigger onCreate() method.
package com.example.dbdemo.test;

import com.example.dbdemo.DBHelper;

import android.test.AndroidTestCase;

public class DBHelperTester extends AndroidTestCase {

public void testDB() throws Exception {
DBHelper helper = new DBHelper(this.getContext());
helper.getWritableDatabase();
}
}

then the new demo.db file will be found in folder /data/data/<package>/databases/demo.db, use the tools sqlite expert to check the table is created successfully.

Now change above code to make the version to 2 in the constructor,and add some db changes in method onUpgrade() which will be triggered if db version changes

package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 2);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("create table teacher(tid integer primary key autoincrement,name varchar(20))");
db.execSQL("alter table person add address varchar(20) null");
}

}

run above test method again, found a new table and a new field are created.

This is a simple class to insert a record

public class MyDbService {

private DBHelper dbHelper;

public MyDbService(DBHelper dbHelper) {
this.dbHelper = dbHelper;
}

public void save() {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL("insert into person (name) values (?)",
new Object[] { "testuser" });
}
}
  • TBC

Spring MVC restful tips - 406 error, cannot put email in the url


  • 406 not acceptable error

need to add the following configuration

<mvc:annotation-driven
content-negotiation-manager="contentNegotiationManager">
</mvc:annotation-driven>

<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
<!-- resolve not acceptable issue -->
<bean id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>text/xml;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>

this is jackson dependencies:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${jackson.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<spring.core.version>3.2.4.RELEASE</spring.core.version>
<log.version>1.2.17</log.version>
<hibernate.version>3.6.10.Final</hibernate.version>
<jackson.version>1.9.13</jackson.version>
</properties>


  • avoid URI using dots to be truncated
  @RequestMapping( value = "/users/{email:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE )

Thursday, November 27, 2014

Configure encrypted DB password in spring

If still use XML to configure spring container, we have to put password with plain text in the spring configuration file which is not suitable for production env, this article will mix javaconfig with XML configuration file to use encrypted password in the configuration file.

1. add following bean to spring XML configuration file

<!-- be sure to include the JavaConfig bean post-processor -->
    <bean class="org.springframework.config.java.process.ConfigurationPostProcessor"/>

refer to http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch06.html

2. specify a @configuration class for post processor

<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource" />
<qualifier value="myDS" /> <!-- for multiple datasource -->
</bean>

<tx:annotation-driven transaction-manager="txManager" />
  <!-- this is post processor to retrieve encrypted password and decrypt it -->
<bean class="wx.poc.MyDataSourceConfig" />

3. implement the post processor class to use c3p0

package wx.poc;

import javax.sql.DataSource;
import ..........;

@Configuration
@Import(PropertyPlaceholderConfigurer.class)
public class MyDataSourceConfig
{

    @Value("${jdbc.url}")
    private String databaseUrl;

    @Value("${jdbc.driverClassName}")
    private String driverClass;

    @Value("${jdbc.username}")
    private String user;

    @Value("${jdbc.password}")
    private String password;

    @Value("${c3p0.acqincrement}")
    private int acqIncrement;

    @Value("${c3p0.acqretryattempts}")
    private int acqRetryAttempts;

   // omit other c3p0 settings

    @Bean(name = "myDataSource")
    public DataSource dataSource()
    {
        try
        {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setJdbcUrl( databaseUrl );
            ds.setDriverClass( driverClass );
            ds.setUser( user );
            ds.setPassword( getDecryptedPassword() );

            // pool settings
            ds.setAcquireIncrement( acqIncrement );
            ds.setAcquireRetryAttempts( acqRetryAttempts );
            // omit other setters.

            return ds;
        }
        catch (Exception e)
        {
         }
    }

    private String getDecryptedPassword()
    {
        // decrypt the configured encrypted password
    }
}

4. property file 

# jdbc configuration
jdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://<server>:<port>;databaseName=<db name>;
jdbc.username=db_username
jdbc.password=<encrypted password>

c3p0.acqincrement=3
c3p0.acqretryattempts=5
// omit other c3p0 properties

Friday, November 21, 2014

Setup Tectia SSH Server/Client

------
OS: windows XP
Tectia server/client evaluation : download from http://www.ssh.com/index.php/evaluation-downloads.html
------
tectia client installation:
unzip the installation package tectia-client-6.3.0.76-windows-upgrd-eval.zip
run installer file ssh-tectia-client-6.3.0.76-windows.msi with admin permission
select Typical installation until the installation is completed.











The two new icons,'Tectia-SSH Terminal' and 'Tectia-Secur File Transfer', will be shown on the desktop if installation is successful.
------
tectia server installation:
unzip the installation package tectia-server-6.3.0.76-windows-upgrd-eval.zip
double click the installer file ssh-tectia-server-6.3.0.76-windows.msi
select Typical installation until the installation is completed.
reboot computer.
------
make connection to ftp server
double click icon 'Tectia - SSH Terminal' on the desktop
press enter or space key to prompt up 'connect to server' dialogue
input 127.0.0.1 for 'Host Name' and a username for 'User Name'
and select 'Proceed with the connection and save the key for the futuer use.'


















key in password for above account.
------
create connection profile:
select menu Profiles - Add Profile...





key in 'TestProfile' for profile name, 127.0.0.1 for host name

click 'Save' button to save above configuration.

click 'Keys and Certificates' menu on the left navigation, 
click 'New Key...' button on tab 'Keys and Certificates'
click 'Next' button
click 'Upload' button to load the pub key generated.

------
test ftp connection:

the command useage:
> sftpg3 <profile name>    
take note profile name is case-sensitive.
------
use GUI to play FTP
double click 'Tectia - Secure File Transfer' icon on the desktop
select 'TestProfile' to make connection.











select menu 'Window' - 'New File Transfer'

Thursday, August 14, 2014

Table Generator in JPA

use a self-defined table for identity generation on any database, which means it is portable across databases.

1) firstly, create a table to store the id will be generated.

create table tb_id_generator (
tb_name varchar(50) not null primary key,
tb_id int(20) not null
);

insert into tb_id_generator(tb_name,tb_id) values ('table1',1);
insert into tb_id_generator(tb_name,tb_id) values ('table2',1000000);

mysql> select * from tb_id_generator;
+---------+---------+
| tb_name | tb_id   |
+---------+---------+
| table1  |           1  |
| table2  | 1000000 |
+---------+---------+
2 rows in set (0.00 sec)


2) specify strategy in entity object.

------------- PersonEO -------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person")
public class PersonEO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen")
@TableGenerator(name = "person_id_gen", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table1")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

person_id_gen: the name of stragety
tb_id_generator: the table name to store id
table1:  to generate id for entity PersonEO

--------------- Person2EO--------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person2")
public class Person2EO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen2")
@TableGenerator(name = "person_id_gen2", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table2")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

3)  use multiple data source, and multiple threads in single data source to run it, found it is working well in multiple threads