среда, 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.

Most popular

Authors