суббота, 6 августа 2011 г.

Alfresco: strong text search with wildcard using lucene

My new problem is not very difficult, but have interesting solution (I think :)). For example we have four objects in our alfresco repository that have next titles:
  • James John
  • Li Joan
  • Johan Jazz
  • Bill

For get all objects that begins from 'Joh', I use the next query:
+TYPE:"my:object" +@cm\:title:"joh*"

The result of the query:
  • James John
  • Johan Jazz
  • Li Joan

'Joan' doesn't contain 'joh' in title. For solve that I modify my query:
+TYPE:"my:object" +@cm\:title:"[joh*]"

This query returns the right result:
  • James John
  • Johan Jazz

You can find more lucene tricks in wonderful book that called: "Lucene in Action" :)

вторник, 2 августа 2011 г.

Quartz: running jobs sequentially

To initialize (import dictionaries, create system users and roles, etc) our system we use quartz. But yesterday after I've created new job I saw that my new job depends on other jobs. Still I don't get how can I create dependencies over jobs and I think that the usage of dependencies in jobs is an ugly solution. The solution is simply to run jobs sequentially - one by one. But I couldn't find fast way to do that. One job runs in one thread and if I set threads count to 1, then I solve my problem. Here is the code to do that:

Properties schedProps = new Properties();
schedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
schedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", "1");
StdSchedulerFactory factory = new StdSchedulerFactory(schedProps);
Scheduler initScheduler = factory.getScheduler();
Now all jobs in initScheduler will run one by one.
It's simple and actually work :)

вторник, 28 июня 2011 г.

Generate changelog from Bamboo with Mercurial

Hi!

We use bamboo(v.3) for supports continuum integration in our project and mercurial as control version system. We wrote some core functions for the project and now should test it. Testers want to see what's new we do in new build. For this reason developers should wrote changelog for new build. But we are lazy developers and don't want to do this :) Unfortunately, bamboo can not generate changelog for mercurial. For make this, I added new stage to bamboo and create one script task in it.

Bamboo has list of global variables that you can use in scripts. Full list of these variables you can see here. I interested two of these variables:
bamboo.repository.revision.number - The revision number
repository.previous.revision.number - The previous revision number (might not exist if for example is initial build)
Now, I simple need to get changes between these revisions. Mercurial has powerful command:
hg log. It supports different templates. I use next command to get log:
hg log -r ${bamboo.repository.previous.revision.number}:${bamboo.repository.revision.number} --template "{rev}:{author} - {desc}\n" -P ${bamboo.repository.previous.revision.number}
I use flag -P to exclude revision from previous build.

Our build server works under Windows Server 2008 and I wrote batch in script task to generate changelog.

IF EXIST C:\changelog.txt (
  COPY C:\changelog.txt C:\changelog.txt.tmp
)

ECHO Build ${bamboo.buildNumber} - ${bamboo.buildTimeStamp} > C:\changelog.txt

IF ${bamboo.repository.previous.revision.number}==${bamboo.repository.revision.number} (
   echo [No changes] >> C:\changelog.txt
) ELSE ( 
   hg log -r ${bamboo.repository.previous.revision.number}:${bamboo.repository.revision.number} --template "{rev}:{author} - {desc}\n" -P ${bamboo.repository.previous.revision.number} >> C:\changelog.txt
)

ECHO ++++++++++++++++++++++++++ >> C:\changelog.txt
ECHO. >> C:\changelog.txt

IF EXIST C:\changelog.txt.tmp (
  TYPE C:\changelog.txt.tmp >> C:\changelog.txt
  DEL C:\changelog.txt.tmp
)

пятница, 29 апреля 2011 г.

Process workflow with alfresco and actitviti

Alfresco release new version of their ECM - 3.4.e. Major feature of this release is integration with activiti BPM platform. That's a good news, because JBPM 3 is obsolete. Of course, I try to use it and have some problems ;) In our projects we use WorkflowService to control business processes. To move workflow from one task to another we have to end current task and specify the transition name(more details you can find here:
    workflowService.endTask(task.getId(), "transitionName");

But when we migrate simple workflow from jbpm to activiti, we got exception :(

I start to discover alfresco sources and found interesting method in activiti workflow component:
    private WorkflowTask endNormalTask(String taskId, String localTaskId, String transition)
    {
        // Retrieve task
        Task task = taskService.createTaskQuery().taskId(localTaskId).singleResult();
        
        if(task == null)
        {
            String msg = messageService.getMessage(ERR_END_UNEXISTING_TASK, taskId);
            throw new WorkflowException(msg);
        }
        
        // Signal the transition on the task
        if (transition != null && 
                ActivitiConstants.DEFAULT_TRANSITION_NAME.equals(transition)==false)
        {
            // Only 'Next' is supported as transition.
            String msg = messageService.getMessage(ERR_END_TASK_INVALID_TRANSITION, transition, taskId, ActivitiConstants.DEFAULT_TRANSITION_NAME);
            throw new WorkflowException(msg);
        }
        setOutcome(task);
        taskService.complete(localTaskId);
        // The task should have a historicTaskInstance
        HistoricTaskInstance historicTask = historyService.createHistoricTaskInstanceQuery().taskId(task.getId()).singleResult();
        return typeConverter.convert(historicTask);
    }

YOOHOO! Now, you can use only "Next" keyword to move activiti workflow and code to end task should looks like this:
     workflowService.endTask(task.getId(),ActivitiConstants.DEFAULT_TRANSITION_NAME);

вторник, 16 ноября 2010 г.

Share smartgwt js over several gwt modules

When you create smartgwt module you have the following lines in your *.gwt.xml file:

...
<inherits name="com.smartgwt.SmartGwt"/>
...

Everything works fine. When you load your module at the first time, the browser gets all smartgwt js libs(ISC_*.js) and caches it. In our application we have got five gwt modules and all of them are using smartgwt. And when we load each module for the first time, it is getting 2 MB of js files. For our five modules browser downloads over 10 MB of smartclient libs. That's too much! To solve this problem I discover SmartGwt.gwt.xml. It inherits from module that called SmartClientDefault:


<module>
<script src="sc/modules/ISC_Core.js"/>
<script src="sc/modules/ISC_Foundation.js"/>
<script src="sc/modules/ISC_Containers.js"/>
<script src="sc/modules/ISC_Grids.js"/>
<script src="sc/modules/ISC_Forms.js"/>
<script src="sc/modules/ISC_RichTextEditor.js"/>
<script src="sc/modules/ISC_Calendar.js"/>
<script src="sc/modules/ISC_DataBinding.js"/>
</module>

And that's our problem. All js have relative paths. But Smartgwt has wonderful module that called SmartGwtNoScript that does not have a dependence on SmartClientDefault and we can include all scripts using absolute paths!
The solution is to replace inherits from com.smartgwt.SmartGwt by the following code in all modules that need smartgwt(or create one root module):


<inherits name="com.smartgwt.SmartGwtNoScript"/>
<script src="/mod/sc/modules/ISC_Core.js"/>
<script src="/mod/sc/modules/ISC_Foundation.js"/>
<script src="/mod/sc/modules/ISC_Containers.js"/>
<script src="/mod/sc/modules/ISC_Grids.js"/>
<script src="/mod/sc/modules/ISC_Forms.js"/>
<script src="/mod/sc/modules/ISC_RichTextEditor.js"/>
<script src="/mod/sc/modules/ISC_Calendar.js"/>
<script src="/mod/sc/modules/ISC_DataBinding.js"/>

вторник, 9 ноября 2010 г.

Simplify if

From time to time we write unoptimized code. For program to be cute it is fun to be optimized.
For example this is a possible way to write if sequence:

if (p.equals("Completed")){
return true;
} else {
return false;
}

But to be optimized the method body should look like this:

return "Completed".equals(p);


What's really interesting is that it's not only looking good. Indeed it helps you to evade null pointer exception in case when p equals null. We are not able to call method from null - it'll throw exception, but when using equals() method this would be processed correctly.

To study this in depth we can explore StringUtils.java (the one that is in org.apache.commons.lang package).


public static boolean equals(String str1, String str2) {
return str1 == null ? str2 == null : str1.equals(str2);
}


Lets look at the method in JAVA source code and the values it may return:

StringUtils.equals(null, null) = true
StringUtils.equals(null, "abc") = false
StringUtils.equals("abc", null) = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false


It returns true in case of equality. So, we can use this or the same logic to write our optimized pieces of code.

Have fun, optimize :)

четверг, 4 ноября 2010 г.

Height for DateChooser (Smart GWT)

In our project we are using Smart GWT component (DateChooser) which allows user to select dates ranges. It looks like this:
date chooser height bug.

Smart GWT has a bunch of useful things (datasources, for example). But when I need some UI customization, sometimes I am having troubles :) And now I've got problems with height of the DateChooser component.

Both date choosers have height 200 px, but the right one is smaller then the left because it has only five week rows. Our tester is very carping guy :) though he had posted a bug in the bug tracker. I had spent some time and found a solution. All that I needed was to wrap DateChooser in a layout. I wrote simple helper method for doing this:

    public static Canvas wrapDateChooser(DateChooser dateChooser, String width, String height) {
        VLayout layout = new VLayout();
        layout.addMember(dateChooser);
        layout.setWidth(width);
        layout.setHeight(height);
        dateChooser.setHeight(height);
        return layout;
    }

Result:
fixed date chooser bug

Go and close bug in JIRA, YEAH!

пятница, 24 сентября 2010 г.

Wrote test, use mockito...drink mockito!

Hi all!

In our project we have parts that are written by our outsource partners. We are using their services in our code. Services injected from spring to our beans.

Yesterday, they made a weekly commit, and I've found out that my tests were broken :( One of their developer had commented my stub that implemented their interface. They said that he didn't want to change any code although I needed my stub to collect and analyze input in it.

I started solving this problem learning about mock objects. On my previous work, one of my coworkers used mockito and he liked it a lot :) I've found the information about it here: site. I suppose it's good that the last version was released in may. Also, I like projects that have good documentation and have *-all.jar. Mockito has all of that!

I needed only one method from two interfaces. And I've written simple mock objects and inject this mocks in spring.


SomeService mockService = Mockito.mock(SomeService.class);
Mockito.doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String[] users = (String[])invocation.getArguments()[0];
String title = (String)invocation.getArguments()[1];
//Collect information here.
return null;
}
}).when(mockService).
someMethod(Mockito.<string[]>any(), Mockito.anyString());

Also, I have got some troubles when injecting this service in spring: I could not do something like this:
myService.setSomeService(mockService);

because I am using @Transactional and having Proxy object. After some google searches I've found solution for this(I think, that it's not ideal, because I found some info about @Configurable beans and wrote TODO ;))

((MyServiceImpl)( ((Advised)myService).getTargetSource().getTarget())).setSomeService(mockService);


I commited this and...drink mockito :)!

среда, 1 сентября 2010 г.

Authentication problems in multithreading

In my application, I have a tree widget that using ajax for loading nodes. Tomcat create some threads to service requests. Each requests validate security ticket that stores in http session:

authenticationService.validate(ticket, null);

And some threads can not validate this ticket and service throws AuthenticationException. If you have particular problem, then you should look at alfresco's jira: ALF-3789. This bug was fixed in revision 21065 at trunk. To fix it in my code, I simple override bean:

<!--Override alfresco-->
    <bean name="ticketsCache" class="org.alfresco.repo.cache.EhCacheAdapter">
       <property name="cache">
          <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
             <property name="cacheManager">
                <ref bean="internalEHCacheManager" />
             </property>
             <property name="cacheName">
                <value>org.alfresco.cache.ticketsCache</value>
             </property>
          </bean>
       </property>
    </bean>

After this all works fine :)

пятница, 20 августа 2010 г.

Fun with alfresco types.

Good day!

Yesterday, I had some fun with alfresco types and permissions. I've created simple type in my model that looks like this:
<type name="my:simpleType">
  <title>Simple type</title>
  <properties>
    <property name="my:prop">
       <type>d:int</type>
    </property>
  </properties>
</type>

I've included it as child-association to the other type and tried to create it:
Node simpleProp = parentNode.addProperty("my:parentProp", "my:simpleType");
  simpleProp.setProperty("my:prop", 1);//AccessDeniedException here!

But, when I ran JUnit test, it threw AccessDeniedException on setProperty for that node. I started debugging alfresco sources, but still I couldn't understand what is going on. Why can I create parent node, add node to association, but can not set property for this node???!!! I tried to use NodeService without JCR API, but I've got the same trouble. Then, I discovered all sources of alfresco's permission subsystem(PermissionServiceImpl, SecurityProvider, etc) with acegi, but still in vain :(

By the end of the day, I have got two cigarettes and zero ideas. After first cigarette, I was looking at my model trying to find problem and finally I've got it! My new type had no parent type! When I added
<parent>cm:content</parent>

everything worked fine! That was hard day: one day - one string...fast work :)

среда, 4 августа 2010 г.

Search content by dates in alfresco using CMIS API

Hi all!

I have a new project that use Alfresco as a backend to store content. I need select content by date. User specifies dates and the service should find and display it. I've chosen CMIS API. It supports query language like SQL to get content. But I've got a problem. I need to specify the date in my query, that looks like this:
SELECT * FROM my:content WHERE my:modifyDate < 01.01.2010
My problem is a date format. In alfresco wiki, I found this description:
datetime literals Based on SQL-92 with 'T' replacing in ,
But it doesn't work. Why???!!! Because I had simply copy-pasted the format for SimpleDateFormat: yyyy-MM-dd'T'HH.mm.ss.SSS. Next hour I could not get why it doesn't work. That format is wrong and must be: yyyy-MM-dd'T'HH:mm:ss.SSS'Z' Also, in alfresco SVN and I've found a good helper class for this: org.alfresco.util.CachingDateFormat. But I didn't hav it in my build of alfresco(I use community build version 3.3, but it was build early). When I upgrade, I will use this class, but for now I wrote simple type helper that looks like this:
package com.blogspot.jajatips.alfresco.util;

import org.apache.commons.lang.time.FastDateFormat;
import java.util.Date;

/**
 * Date: 03.08.2010
 * @author Dmitry N. Pokidov
 */
public class TypeHelper {
    public static String formatDate(Date date) {
        //TODO: CachingDateFormat.getCmisSqlDatetimeFormat()
        FastDateFormat cmisDateFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
        return new StringBuilder("TIMESTAMP '").append(cmisDateFormat.format(date)).append("'").toString();
    }
}
As you can see, I use FastDateFormat from common-lang, that cache date formats. Also, their formats are thread safe, which is very important if you have some static queries.

Well, I'm going to request to make changes in SimpleDateFormat in alfresco wiki, so no one would have this trouble :)

UPD: Good news, I have fixed alfresco wiki page :)

понедельник, 18 января 2010 г.

Getting changes from collection

Good morning :)

Usually to update some entities in database I use service that must be calculate changes from new and old object and update changes in database(using DAO, for example). And the common task is update collections. I wrote simple class that calculate changes from old and new collections and create three collections: to delete, to update and to create. You can see class below and some description for it

package com.blogspot.jajatips.utils;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class ChangesCollections<T> {
 private static class CollectionFactory<T> {
  private static final Log log = LogFactory.getLog(CollectionFactory.class);
  private Class<? extends Collection> classCollection = ArrayList.class;

  private CollectionFactory(Class<? extends Collection> classCollection) {
   this.classCollection = classCollection;
  }

  @SuppressWarnings("unchecked")
  public Collection<T> getNewCollection() {
   try {
    return classCollection.newInstance();
   } catch (Exception e) {
    log.warn("Can not create collection for class [" + classCollection.getName() + "], create java.util.ArrayList");
    return new ArrayList<T>();
   }
  }
 }

 private Collection<T> deleteCollection;
 private Collection<T> addCollection;
 private Collection<T> updateCollection;

 public ChangesCollections(Collection<T> oldCollection, Collection<T> newCollection) {
  CollectionFactory<T> factory = new CollectionFactory<T>(oldCollection.getClass());

  this.deleteCollection = factory.getNewCollection();
  this.addCollection = factory.getNewCollection();
  this.updateCollection = factory.getNewCollection();

  addCollection.addAll(newCollection);
  deleteCollection.addAll(oldCollection);
  Iterator<T> addIt = addCollection.iterator();
  while (addIt.hasNext()) {
   T addObject = addIt.next();
   if (contains(oldCollection, addObject)) {
    updateCollection.add(addObject);
    deleteCollection.remove(addObject);
    addIt.remove();
   }
  }
 }

 public Collection<T> getDeleteCollection() {
  return deleteCollection;
 }

 public Collection<T> getAddCollection() {
  return addCollection;
 }

 public Collection<T> getUpdateCollection() {
  return updateCollection;
 }

 private boolean contains(Collection<T> collection, T object) {
  if (object.getClass().isAssignableFrom(Predicate.class)) {
   return CollectionUtils.exists(collection, (Predicate) object);
  }

  return collection.contains(object);
 }
}

First of all, to build this code you need next libraries: commons-collections and commons-logging. Also, I like generics and use it in all my code, because I like compile errors more than runtime errors :).
Now, about the code. To create collections, I use inner class CollectionFactory, that try to create class of collection same as original collection, if it can not do this, it create standard java.util.List. As you can see, code is simple. All that it done is iterate over collections and put elements in other three collections.

I hope that this class helps you in your projects :)

среда, 18 ноября 2009 г.

Good examples of Java EE patterns.

Good Day.

In the last message from SDN, I received information about "Java EE Patterns and Best Practices Project". That is excellent idea, with excellent realization. I recommend to all developers that use Java EE to see this examples.

To get it, you should have mercurial and checkout repository, you can do it with following command:

hg clone https://hg.kenai.com/hg/javaee-patterns~hg .

To view and run examples, you need Netbeans IDE.

Also you can discuss examples here: http://kenai.com/projects/javaee-patterns?cid=e9803

понедельник, 31 августа 2009 г.

Write compressing rolling file appender for log4j

UPD: One good man leave a good comment :)

I think this is now supported in the standard;

http://logging.apache.org/log4j/companions/extras/apidocs/org/apache/log4j/rolling/TimeBasedRollingPolicy.html


Good day!

On the last week, we gave our software to support specialists. They installed the program to production servers. And over three thousand users began to work with it. At the on of that week, I didn't hear nothing about our product. This is good the good sign! :) But, we have one little problem, log file that wrote program had size little more 1Gb!!! This is not very good result :) Server's administrator sent me this log file. I analyzed it. Unfortunately, all information in it was very useful and didn't find anything that I can remove from log.

For logging, we use log4j. If you never use log4j, I recommend to read official document. Log4j have interface Appender. When you want log something, you call some code, looks like this:

...
import org.apache.log4j.Logger;

public class YourClass {
  private static final Logger log = Logger.getLogger(YourClass.class)
  ...
  public void someMethod() {
    log.info("Call someMethod()");
  }
  ...
}

Log4j pass all logging events through appenders, that you configure. By default, you have many appenders, that can send log over SMTP, JMS and do other interesting things. But, this list has no appenders that can periodically compress log file. To solve this problem, I write my own appender, that can periodically compress current log file. Because, I need compress only files, I inherit my appender from FileAppender and override subAppend() method. I don't speak more and simple show you result code:)

import org.apache.log4j.FileAppender;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.zip.GZIPOutputStream;

/**
 * Appender that periodically compress current log.
 * You need only pass one setting - period after that log should compress.
 * Available periods are MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
 *
 * @author Pokidov.Dmitry
 *         Date: 27.08.2009
 */
public class CompressingRollingFileAppender extends FileAppender {
 enum Period {MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY}

 private GregorianCalendar rollDate;
 private Period period = Period.HOURLY;
 private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm");

 @Override
 public void activateOptions() {
  super.activateOptions();
  setAppend(false);
 }

 /**
  * Period for rolling. By default - {@code HOURLY}
  * @param period new period. Available values: MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
  */
 public void setPeriod(String period) {
  try {
   this.period = Period.valueOf(period);
  } catch (IllegalArgumentException e) {
   LogLog.warn("No period with name [" + period + "]", e);
  }

  LogLog.debug("Set roll period to [" + this.period + "]");
 }

 @Override
 public void setAppend(boolean flag) {
  LogLog.warn("Compression rolling appender doesn't support append option(always set to false)");
 }

 protected void rollOver() {
  String currentFileName = getFile();
  File currentFile = new File(currentFileName);
  File zipFile = new File(currentFileName + "." + dateFormat.format(new Date()) + ".gz");
  try {
   /*
     actually, I use helper that read all bytes, using NIO.
     see http://jajatips.blogspot.com/2008/11/reading-from-inputstream.html
   */
   FileInputStream stream = new FileInputStream(currentFile);
   byte[] content = new byte[](stream.available());
   stream.read(content);

   GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(zipFile));
   gzipOutputStream.write(content, 0, content.length);
   gzipOutputStream.finish();
   gzipOutputStream.flush();
   gzipOutputStream.close();

   setFile(currentFileName, false, getBufferedIO(), getBufferSize());
   rollDate.add(getDateFieldByPeriod(), 1);
  } catch (IOException e) {
   LogLog.error("Error compress current log file [" + currentFile + "] to zip file [" + zipFile + "]", e);
  }
 }

 @Override
 protected void subAppend(LoggingEvent event) {
  if (rollDate == null) {
   rollDate = new GregorianCalendar();
   rollDate.add(getDateFieldByPeriod(), 1);
  }

  GregorianCalendar now = new GregorianCalendar();
  if (rollDate.before(now)) {
   rollOver();
  }

  super.subAppend(event);
 }

 private int getDateFieldByPeriod() {
  switch (period) {
   case MINUTLY:   return GregorianCalendar.MINUTE;
   case HOURLY:    return GregorianCalendar.HOUR;
   case DAILY:     return GregorianCalendar.DAY_OF_MONTH;
   case WEEKLY:    return GregorianCalendar.WEEK_OF_MONTH;
   case MONTHLY:   return GregorianCalendar.MONTH;
   default: return GregorianCalendar.MINUTE;
  }
 }
}

Also, I wrote test, because I don't want situation when error in logging subsystem cause crash in the application :)

import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import java.io.File;
import java.io.StringReader;
import java.io.IOException;
import java.util.Properties;

/**
 * @author Pokidov.Dmitry
 *         Date: 28.08.2009
 */
public class CompressingRollingFileAppenderTest extends TestCase {
 private static final String LOG_DIR="log";

 private static class LogRun implements Runnable {
  private Logger log = Logger.getLogger(LogRun.class);

  @Override
  public void run() {
   for (int i = 0; i < 150; i++) {
    log.info("Log " + i);
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     log.error("Interrupt", e);
    }
   }
  }
 }

 /**
  * Create 2 threads and create some log in it.
  */
 public void testAppender() {
  File dirLog = new File(LOG_DIR);
  try {
   for (File f : dirLog.listFiles()) {
    if (!f.delete()) {
     System.err.println("Cann't delete old log file [" + f.getName() + "]");
     Assert.fail("Cann't delete old log file [" + f.getName() + "]");
    }
   }

   PropertyConfigurator.configure(getLogProperties());

   Thread logThread1 = new Thread(new LogRun());
   Thread logThread2 = new Thread(new LogRun());

   logThread1.start();
   logThread2.start();

   logThread1.join();
   logThread2.join();
  } catch (Exception e) {
   e.printStackTrace();
   Assert.fail("Error while execute test: " + e.getMessage());
  }

  Assert.assertEquals(3, dirLog.list().length);
 }

 private Properties getLogProperties() throws IOException {
  Properties result = new Properties();
  String cfg = "log4j.debug=TRUE\n" +
               "log4j.rootLogger=INFO, main\n" +
               "\n" +
               "log4j.appender.main=com.otr.security.server.utils.CompressingRollingFileAppender\n" +
               "log4j.appender.main.Period=MINUTLY\n" +
               "log4j.appender.main.Append=true\n" +
               "log4j.appender.main.layout=org.apache.log4j.PatternLayout\n" +
               "log4j.appender.main.layout.ConversionPattern=[%d] %5p [%t] (%F:%L) - %m%n\n" +
               "log4j.appender.main.File=" + LOG_DIR + "/test.log";

  result.load(new StringReader(cfg));

  return result;
 }
}
And the production log4j.properties:
log4j.debug=TRUE
log4j.rootLogger=INFO, main

log4j.logger.com.mchange=ERROR
log4j.logger.org.springframework=ERROR

log4j.appender.main=server.utils.CompressingRollingFileAppender
log4j.appender.main.Period=DAILY
log4j.appender.main.File=log/server.log
log4j.appender.main.layout=org.apache.log4j.PatternLayout
log4j.appender.main.layout.ConversionPattern=[%d] %5p [%t] (%F:%L) - %m%n

среда, 1 июля 2009 г.

Increase perfomance for insert operations to database

Hi all!

In this post, I would like to tell about the way to optimize INSERT operation to database.


In our project we use postgresql 8.3. I have big problems with speed on some operations. When I profiled the application, I find out that the biggest amount of time spent for JDBC operations. We getting data from external systems, and often we have more than 300000 inserts to table. For work with database we use JDBCTemplate from Spring framework.


All that you should do: realize class that implements BatchPreparedStatementSetter. I create universal abstract class that implements this interface.


abstract public class AbstractPreparedStatementSetter implements PreparedStatementSetter, BatchPreparedStatementSetter {
private List objects;

protected AbstractPreparedStatementSetter(List objects) {
this.objects = objects;
}

public void setValues(PreparedStatement preparedStatement) throws SQLException {
fillStatement(objects.get(0), preparedStatement);
}

@Override
public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
fillStatement(objects.get(i), preparedStatement);
}

@Override
public int getBatchSize() {
return objects.size();
}

abstract public void fillStatement(T object, PreparedStatement statement) throws SQLException;
}


Concrete implementation example:

public class RegistrationEntrySetter extends AbstractPreparedStatementSetter {
public RegistrationEntrySetter(List objects) {
super(objects);
}

@Override
public void fillStatement(RegistrationEntry entry, PreparedStatement statement) throws SQLException {
long time = entry.getActionDate() == null ? System.currentTimeMillis() : entry.getActionDate().getTime();
statement.setString(1, entry.getLogin());
statement.setTimestamp(2, new Timestamp(time));
statement.setShort(3, entry.getActionResult());
statement.setString(4, entry.getDeviceName());
String comment = entry.getComment();
if(comment.length() > 8190)
comment = comment.substring(0, 8190);
statement.setString(5, comment);
statement.setShort(6, entry.getAcessLevel().getLevel());
statement.setString(7, entry.getObjectType());
statement.setString(8, entry.getObjectCategory());
statement.setString(9, entry.getObjectId());
statement.setInt(10, entry.getEventType().getId());
statement.setString(11, entry.getActionName());
statement.setString(12, entry.getUserName());
statement.setShort(13, entry.isNotification() ? (short)1 : (short)0);
statement.setString(14, entry.getRemoteAddress());
statement.setString(15, PasswordEncoder.encode(entry.toString()));
}
}


In spring xml context, I describe all implementations for statements setters for my classes.


<bean id="preparedStatementsSetter" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="com.blogspot.RegistrationEntry">
<value>com.otr.security.server.audit.sql.RegistrationEntrySetter</value>
</entry>
.....
.....
</map>
</constructor-arg>
</bean>


And in my dao I can get setter for concrete class for list of objects.
In this way we have one advantage, all setters are singleton and we have reusable prepared statements.

The sample dao may look like this:

public class AbstractDAO {
//spring fields
private SimpleJdbcTemplate template;
private Map<Class, Class<? extends AbstractPreparedStatementSetter>> statementSetters;
//spring fields

public int[] batchUpdate(String sql, List objects) {
if (!objects.isEmpty()) {
try {
AbstractPreparedStatementSetter setter = getSetter(objects, objects.get(0).getClass());
if (setter != null) {
return template.getJdbcOperations().batchUpdate(sql, setter);
}
} catch (Exception e) {
log.error("Error while construct sql setter", e);
}
}

return new int[]{};
}

private AbstractPreparedStatementSetter getSetter(List objects, Class objectClazz)
throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Class<? extends AbstractPreparedStatementSetter> setterClazz = statementSetters.get(objectClazz);

if (setterClazz != null) {
Constructor<? extends AbstractPreparedStatementSetter> c = setterClazz.getConstructor(List.class);
return c.newInstance(objects);
} else {
log.warn("======================NO SQL SETTER FOR CLASS " + objectClazz.getName() + "======================");
return null;
}
}
}

вторник, 21 апреля 2009 г.

Create hyperlink with browsing support.


Yesterday, one of my tasks was to display hyperlink to local file. When user clicks hyperlink, program have to open file in an associated application. Earlier I have been writing desktop applications on QT and I haven't had any problems. But it is not very simple in Java(Swing). First, I've created label and put html as text:



JLabel label = new JLabel("<html><a href=\"someurl.com\">link</a>");


When I launched an application, I turned to be very happy because I saw hyperlink. Furthermore, my delight was anticipatory :( Label was only looking like hyperlink :(



My colleagues told me to use jdic. This is a good library, but after some time I found the solution in JDK. JDK has the class java.awt.Desktop that was introduced in the version 1.6. The desktop has static method browse(URI uri). It opens the uri in a default browser on your system. Besides, you can use static method open(File f) to open file in an associated application.



When I solved trouble with URL browsing, I back to the problem with look'n'feel - label with html has underline text, always(but need underline only when cursor on hyperlink). Also, label returned text that was containing html tags which was not very good. I had some problems to underline the text in the label. I found solution on java.sun.com



When I've solved all problems I wrote two classes. One for the look'n'feel and the other(inherited from first) for browsing URLs.




public class HyperlinkView extends JLabel {
Font srcFont;

public HyperlinkView() {
this("");
}

public HyperlinkView(String text) {
super(text);
setForeground(Color.BLUE);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
srcFont = HyperlinkView.this.getFont();
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
Font font = HyperlinkView.this.getFont();
Hashtable<TextAttribute, Object> attributes = new Hashtable<TextAttribute, Object>();
attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
HyperlinkView.this.setFont(font.deriveFont(attributes));
}

@Override
public void mouseExited(MouseEvent e) {
HyperlinkView.this.setFont(srcFont);
}
});
}
}



public class HyperlinkLabel extends HyperlinkView {
private String url;

public HyperlinkLabel(String text, String url) {
super(text);
this.url = url;
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
try {
Desktop.getDesktop().browse(URI.create(HyperlinkLabel.this.getUrl()));
} catch (IOException e1) {
JOptionPane.showMessageDialog(HyperlinkLabel.this, "Associated application not found", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
}

public HyperlinkLabel() {
this("", null);
}

public String getUrl() {
return url == null ? "" : url;
}

public void setUrl(String url) {
this.url = url;
}
}

воскресенье, 19 апреля 2009 г.

test syntax highlighter for blogger


public class SimpleClass {
public SimpleClass() {
System.out.println("I'm a simple class!!!");
}
}


If you want the same highlighter in your blogger:
1.Install widget from here: http://fazibear.googlepages.com/blogger.html
2.Reading usage instruction(and do it:)) from here: http://code.google.com/p/syntaxhighlighter/wiki/Usage

Big thanks to developers of syntaxhighlighter.

воскресенье, 12 апреля 2009 г.

Using inner classes

Hi all! In this post I want to tell you about inner classes. Very often, we create static inner classes that contains information about main class. For example:



public class Person {

private String name;

private String occupation;

public static class PersonInfo {

private String info;

public PersonInfo(String name, String occupation) {

info = “His name is ” + name + “ . He is a ” + occupation;

}

public String getInfo() {

return info;

}

}

public Person(String name){

this.name = name;

}

public void setOccupation(String occupation){

this.occupation = occupation;

}

public String getOccupation() {

return occupation;

}

public PersonInfo getInfo() {

return new PersonInfo(name, occupation);

}

}



This is a good example of using inner class. It demonstrates one of the main OOP conception – encapsulation. But it has one problem. If we put this object in some model, model don't know anything about Person and if Person object will be change, model won't know about this. And now I want to show powerful feature of inner class in java – this.outer:


public class Person {

  private String name;

  private String occupation;

  public class PersonInfo {

    public PersonInfo() {}

    public String getInfo() {

      return “His name is ” + this.Person.name +

                      “ . He is a ” + this.Person.occupation;

    }

}


public Person(String name){

  this.name = name;

}


public void setOccupation(String occupation){

  this.occupation = occupation;

}


public String getOccupation() {

  return occupation;

}


public PersonInfo getInfo() {

  return new PersonInfo();

}

}


Now, PersonInfo will return actually information about Person at anytime. And I shouldn't reload model that use it when Person object change. I can use feature this.superClass in anonymous class, too. Notice, that PersonInfo is not static. Actually, in first simple example PersonInfo is standalone class. In the second example, PersonInfo is in Person object.

воскресенье, 18 января 2009 г.

Debug JNLP application

Hi all!

I want to remote debug in jnlp application. This problem solved is very simple. I set environment variable JAVAWS_VM_ARGS to value: -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006

After this, I create new run point in Intelli J IDEA. Type of run point is Remote debug. In run point set set host=localhost and port=5006(or other, ports in JAVAWS_VM_ARGS and run point must be equals).

All is ok, run jnlp and created run point.

суббота, 13 декабря 2008 г.

Global security in WAS 6.1over Active Directory

We need security over Active Directory in our enterprise application wich works on WebSphere Application Server v 6.1.x. When we test our application we use OpenDS(https://opends.dev.java.net/) to emulate Active Directory. But we must replace OpenDS with Active Directory. Every LDAP server has specific parameters. For fast test, I use ldapsearch from OpenDS. I spent more then two hours to get success result.

Success command is: ldapsearch.bat -b "dc=domain,dc=com" -D "cn=username,cn=users,dc=domain,dc=com" "(&(|(cn=username)(samAccountName=username))(objectclass=user))".

When I get user with username username, I move this settings to WebSphere Application Server. Also, user should be in group Domain Administrator.

We must set next settings in WAS(sorry me, but name of parameters are not exact, because I have russian version of WAS):

Administrator username: username

Host: localhost(or other if AD server installed on other host)

LDAP Server Type: Active Directory

Base DN: dc=domain, dc=com

Other DN: cn=username,cn=users,dc=domain,dc=com

Password for connect: domain password for user

In next step we should set filters(Additional Parameters for LDAP registry):

User Filter: (&(|(cn=%v)(samAccountName=%v))(objectclass=user))

Group Filter: (&(cn=%v)(objectcategory=group))

ID user map: user:sAMAccountName

ID group map: *:cn

And now you can set current global security as autonomous LDAP registry!

Most popular

Authors