Wednesday, August 21, 2013

Implementing Parcelable interface

When looking for a way to implement Parcelable interface, I found a great parcelabler tool by Dallas Gutauckis to generate code quickly.

Suppose we have a class:

 public class DO {  
      private Long id;  
      private String name;  
      private String desc;  
      private Date created;  
 }  

Then copy and paste into the textbox and click Build button, the result will be:

 public class DO {  
      private Long id;  
      private String name;  
      private String desc;  
      private Date created;  
   protected DO(Parcel in) {  
     id = in.readLong();  
     name = in.readString();  
     desc = in.readString();  
     long tmpCreated = in.readLong();  
     created = tmpCreated != -1 ? new Date(tmpCreated) : null;  
   }  
   public int describeContents() {  
     return 0;  
   }  
   public void writeToParcel(Parcel dest, int flags) {  
     dest.writeLong(id);  
     dest.writeString(name);  
     dest.writeString(desc);  
     dest.writeLong(created != null ? created.getTime() : -1L);  
   }  
   public static final Parcelable.Creator<DO> CREATOR = new Parcelable.Creator<DO>() {  
     public DO createFromParcel(Parcel in) {  
       return new DO(in);  
     }  
     public DO[] newArray(int size) {  
       return new DO[size];  
     }  
   };  
 }  

Although the options for removing fields won't work, but the tool is useful. Thank you Dallas.

Thursday, August 15, 2013

Custom BaseAdapter and notifyDataSetChanged

When making a custom adapter from BaseAdapter, I got the problem that calling notifyDataSetChanged() doesn't work - new data isn't refreshed as expected.

Below is my code:

 public class RowAdapter extends BaseAdapter {  
   private List<? extends RowDO> data;  
   public RowAdapter(List<? extends RowDO> d) {  
     data = d;  
   }  
   @Override  
   public int getCount() {  
     return data.size();  
   }  
   ...  
 }  
 public class MainActivity extends Activity {  
   private RowAdapter rowAdapter;  
   private List<RowDO> listDO;  
   ...  
   private void displayList(){  
     listDO = getListDO();  
     if (rowAdapter == null) {  
       rowAdapter = new RowAdapter(listDO);  
       ListView listView = (ListView) findViewById(R.id.list);  
       listView.setAdapter(rowAdapter);  
     } else {  
       rowAdapter.notifyDataSetChanged();  
     }  
   }  
   private List<RowDO> getListDO() {  
     List<RowDO> list = new ArrayList<RowDO>();  
     list.add(new RowDO());  
     if (some_conditions) {  
       ...  
     }  
     return list;  
   }  
 }  

After google around, I found Snicolas answer

The list I use in my adapter (data) is a reference to the list created in getListDO() when passing it to adapter at first time. So the next time, when I re-assign the listDO with the new list, the old copy in the adapter is still there.

To correct this, we should keep the original list as below code:

 private List<RowDO> listDO = new ArrayList<RowDO>();  
 private void displayList(){  
   listDO.clear();  
   listDO.addAll(getListDO());  
   if (rowAdapter == null) {   
     rowAdapter = new RowAdapter(listDO);   
     ListView listView = (ListView) findViewById(R.id.list);   
     listView.setAdapter(rowAdapter);   
    } else {   
     rowAdapter.notifyDataSetChanged();   
    }   
 }  

Tuesday, August 13, 2013

Ruby -=- undefined method `[]' for #Enumerable::Enumerator

Repost from Yann Laviolette blog. I got similar problem and his solution works.

Below is his instruction:

When I upgrade to Ruby 1.8.7 I've got some problems. I'm getting the error message undefined method `[]' for #Enumerable::Enumerator.

The problem occurs with the version 1.8.7 of Ruby and it's supposed to be corrected in the version 9. To fix this problem you can either downgrade to Ruby 1.8.6 OR put this piece of code in your environnement.rb within initializer block:

 unless '1.9'.respond_to?(:force_encoding)  
  String.class_eval do  
   begin  
    remove_method :chars  
   rescue NameError  
    # OK  
   end  
  end  
 end  


Wednesday, August 7, 2013

SSH restriction users by IPs

To allow or block users SSH to a server, we can use firewall (iptables or AWS Security Groups).
But there are some needs for some specific users. Below is an example to restrict a group of users to SSH from some IPs.

1. Create a group:
groupadd limitgroup
usermod -a -G limitgroup limituser

2. Configure sshd - /etc/pam.d/sshd
account  required  pam_access.so

3. Configure rules - /etc/security/access.conf
-: limitgroup:ALL EXCEPT 111.165.70.120 112.150.60.0/20

Tuesday, August 6, 2013

ActiveAndroid - Model inheritance

I test ActiveAndroid around and found an issue.

When creating a base super DO class that extends from Model class with some common properties/methods, then create some DOs extending from that base DO.

It turns the extended DOs to miss their properties.

Look into more detail, the problem is from the TableInfo.java

 public TableInfo(Class<? extends Model> type) {  
   mType = type;  
   final Table tableAnnotation = type.getAnnotation(Table.class);  
   if (tableAnnotation != null) {  
     mTableName = tableAnnotation.name();  
   }  
   else {  
     mTableName = type.getSimpleName();  
   }  
   List<Field> fields = new ArrayList<Field>(Arrays.asList(type.getDeclaredFields()));  
   fields.add(getIdField(type));  
   for (Field field : fields) {  
     if (field.isAnnotationPresent(Column.class)) {  
       final Column columnAnnotation = field.getAnnotation(Column.class);  
       mColumnNames.put(field, columnAnnotation.name());  
     }  
   }  
 }  
 private Field getIdField(Class<?> type) {  
   if (type.equals(Model.class)) {  
     try {  
       return type.getDeclaredField("mId");  
     }  
     catch (NoSuchFieldException e) {  
       Log.e("Impossible!", e);  
     }  
   }  
   else if (type.getSuperclass() != null) {  
     return getIdField(type.getSuperclass());  
   }  
   return null;  
 }  

The reason is the above red codes just get its properties but without including its super classes' properties.

So I created a new method getDeclaredFields to replace the getIdField as below:

 /**Get declared fields from the given class & it's ancestors  
  * @param type class  
  * @return  
  */  
 private List<Field> getDeclaredFields(Class<?> type) {  
   List<Field> fields = new ArrayList<Field>();  
   if (type.getSuperclass() != null && type.getSuperclass() != Object.class) {  
     fields.addAll(getDeclaredFields(type.getSuperclass()));  
   }  
   fields.addAll(Arrays.asList(type.getDeclaredFields()));  
   return fields;  
 }  

And replace red lines with:
 List<Field> fields = getDeclaredFields(type);  

Android ContentProvider

When an Android app gets into more complex with AsyncTasks to manipulate the SQLite database, we faced with the database locking issues.

So I googled around and found this discussion, then switched to ContentProvider and it solves the problem.

To implement ContentProvider, the first thing to do is adding authorities to AndroidManifest.xml

 <provider android:authorities="com.mycompany.androidapp.contentprovider.authorities"   
       android:name="com.mycompany.androidapp.data.ContentProviderDb"/>  

The android:authorities="com.mycompany.androidapp.contentprovider.authorities" is just an attribute - whatever unique value - which will be used for the URI to access the data

The class com.mycompany.androidapp.data.ContentProviderDb is extended from android.content.ContentProvider, must implement following required methods:

 public boolean onCreate();  
 public String getType(Uri uri);  
 public Cursor query(Uri uri, String[] projection, String selection,  
                String[] selectionArgs, String sortOrder);  
 public Uri insert(Uri uri, ContentValues values);  
 public int update(Uri uri, ContentValues values, String selection,  
                String[] selectionArgs);  
 public int delete(Uri uri, String selection, String[] selectionArgs);  

Because we need to access the database from different threads, so we must implement the access thread-safe. The simple way is to use synchronized for those query/insert/update/delete methods.

Below is an example

 public class ContentProviderDb extends ContentProvider {  
   private SQLiteOpenHelper openHelper = null;  
   /****************************************  
    * ContentProvider implementation methods  
    * **************************************/  
   @Override  
   public boolean onCreate() {  
     openHelper = new SQLiteOpenHelper(getContext(), Constant.DATABASE_NAME, null , 1){  
       public void onCreate(SQLiteDatabase db){  
       }  
       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){  
       }                 
     };  
     return true;  
   }  
   @Override  
   public String getType(Uri uri) {  
     // TODO Auto-generated method stub  
     return null;  
   }       
   /**Execute query: raw query (select/insert/update/delete), create, select data from table  
    * @return Cursor or null  
    */    
   @Override  
   public synchronized Cursor query(Uri uri, String[] projection, String selection,  
           String[] selectionArgs, String sortOrder) {  
     SQLiteDatabase database = openHelper.getReadableDatabase();  
     Cursor cursor = null;  
     String query = getQuery(uri);  
     String lcQuery = query.toLowerCase();  
     if (lcQuery.startsWith("select ") || lcQuery.startsWith("insert ") ||   
       lcQuery.startsWith("update ") || lcQuery.startsWith("delete ") ) { //raw queries  
       cursor = database.rawQuery(query, null);  
     } else if (lcQuery.startsWith("create ")) { //create tables  
       database.execSQL(query);  
     } else { //query is a table name -> normal select from table  
       cursor = database.query(query, projection, selection, selectionArgs, null, null, sortOrder);  
     }  
     return cursor;  
   }  
   @Override  
   public synchronized Uri insert(Uri uri, ContentValues values) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     long insertedId = database.insert(table, null, values);  
     return Uri.parse("content://" + getAuthority() + "/" + table + "/" + insertedId);   
   }  
   @Override  
   public synchronized int update(Uri uri, ContentValues values, String selection,  
           String[] selectionArgs) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     return database.update(table, values, selection, selectionArgs);  
   }  
   @Override  
   public synchronized int delete(Uri uri, String selection, String[] selectionArgs) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     return database.delete(table, selection, selectionArgs);  
   }  
   /***********************************************  
    * ContentProvider implementation methods - END  
    * *********************************************/  
   /**Get authority  
    * @return authority string  
    */  
   private String getAuthority() {  
     return "com.mycompany.androidapp.contentprovider.authorities";  
   }    
   /**Extract query from uri  
    * @param uri  
    * @return extracted query/table name  
    */  
   private String getQuery(Uri uri){  
     return uri.getPath().replace("/", "");//remove '/'  
   }  
 }  


To create tables and populate the database, we can either add some methods to onCreate of SQLiteOpenHelper in onCreate of ContentProviderDb:

 openHelper = new SQLiteOpenHelper(getContext(), Constant.DATABASE_NAME, null , 1){  
   public void onCreate(SQLiteDatabase db){  
     createTables(); //your own implementation 
     autoPopulate(); //your own implementation 

Or in the activity (with copy from existing database):

 private String database_path = "";  
 if(android.os.Build.VERSION.SDK_INT >= 4.2){  
   database_path = getBaseContext().getApplicationInfo().dataDir + "/databases/";   
 } else {  
   database_path = "/data/data/" + getBaseContext().getPackageName() + "/databases/";  
 }  
 if(!existDataBase()) {  
   try {  
     if (!copyDataBase()) {  
       getContext().getContentResolver();//init the ContentProviderDb to create the database  
       createTables();  
       autoPopulate();  
     }  
   } catch (IOException e) {  
       Log.e("IOException", "exception in copyDataBase() method");  
   }  
 }  
 /**Check if database exist  
  * @return true if the database is existed, false if not  
  */  
 private boolean existDataBase() {  
   File dbFile = new File(getDatabasePath());  
   return dbFile.exists();  
 }  
 /**Get database path  
  * @return path string  
  */  
 private String getDatabasePath() {  
   return database_path + Constant.DATABASE_NAME;  
 }  
 /**Copy the existing database from the data directory  
  * @return true if the database is copied, false if not  
  * @throws IOException  
  */  
 private boolean copyDataBase() throws IOException {         
   File dbFile = getSourceDatabaseFile();// your own implementation
   if (dbFile.exists()) { //copy the database  
     InputStream in = new FileInputStream(dbFile);  
     OutputStream out = new FileOutputStream(getDatabasePath());  
     byte[] mBuffer = new byte[1024];  
     int mLength;  
     while ((mLength = in.read(mBuffer))>0) {  
       out.write(mBuffer, 0, mLength);  
     }  
     in.close();  
     in = null;  
     out.flush();  
     out.close();  
     out = null;  
     return true;  
   }  
   return false;  
 }       


To manipulate the data in the activity, we use ContentResolver instead ContentProvider.

 /**  
  * Creates tables  
  */  
 private void createTables(){                                
     for(String sql : getTableCreationSQLs()){  
         execSQL(sql);  //query starts with "create table "
     }  
 }  
 /**Used for executing query, start with create (not select/insert/delete/update)  
  * @param query  
  */  
 private void execSQL(String query) {  
     query(query, null, null, null);  
 }  
 /**Execute query or select data from table  
  * @param query : query or table name  
  * @param columns  
  * @param whereClause  
  * @param orderBy  
  * @return cursor  
  */  
 private Cursor query(String query, String[] columns, String whereClause, String orderBy){  
   return getBaseContext().getContentResolver().query(getUri(query), columns, whereClause, null, orderBy);  
 }  
 private Uri getUri(String query){  
   return Uri.parse("content://" + getAuthority() + "/" + query);  
 }  


Monday, August 5, 2013

Apache HTTPD, Tomcat configuration and Tapestry 5 app

A client wants to run their Tapestry 5 app as root - i.e http://clientdomain.com/ - instead http://clientdomain.com/clientT5app/

So lets configure the Apache HTTPD

 <VirtualHost *:80>  
     ServerName clientdomain  
     ProxyPass / ajp://localhost:8009/  
 </VirtualHost>  

And Tomcat server.xml configuration:

 <Host name="localhost" appBase="root"  
       unpackWARs="true" autoDeploy="true"  
       xmlValidation="false" xmlNamespaceAware="false">  
     <Context  
         docBase="/var/lib/tomcat6/webapps/clientT5app"   
         path=""  
         reloadable="true"  
     />  
     <Context path="/manager" debug="0" privileged="true" docBase="/var/lib/tomcat6/webapps/manager">  
         <ResourceLink name="users" global="UserDatabase" type="org.apache.catalina.UserDatabase"/>  
     </Context>  

We need to change appBase to another folder (like /var/lib/tomcat6/root) or move clientT5app out of webapps folder in case appBase="webapps" as default to avoid double loadding the clientT5app.

The context manager is used to manage applications.



Sunday, August 4, 2013

Rails 2 debug logger


I need to have clean log for a Rails 2 app. So follow the guide to change the environment.rb:

Rails::Initializer.run do |config|

config.log_level = :debug

From myController:
logger.debug "Debug some thing"

But it didn't work. There's no log in the development.log!

Then switch to 
config.log_level = :warn

From myController:
logger.warn "This works!"

Worked! Don't know why? But that's it.