17 апр. 2009 г.

Blogger. Подсветка синтаксиса - не совсем бесполезная идея.

Ну вот и снова пятница, а рассказать и нечего, хотя... Расскажу ка я о такой полезной вещи для блога, как подсветка синтаксиса. Все айтишники наверняка сталкивались с такой проблеммой как ненаглядность опубликованного програмного кода. Хочется немного разукрасить кодяру :). Есть много различных способов это сделать, но я остановился на двух, по моему мнению найболее удобных.

Первый - с помощью javascript-а, мне понравился проект от Google под скромным названием google-code-prettify. Для активации необходимо добавить в хеадеры строки
<link href='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css' rel='stylesheet' type='text/css'/>
    <script src='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js' type='text/javascript'/> 
Далее необходимо в тег body добавить onload='prettyPrint()'.На примере blogger.com для этого нужно подправить шаблон страницы Layout->Edit HTML. В итоге получаем все, что заключено в теги code или pre с выставленным аттрибутом class='prettyprint' будет отформатировано в соответствие с синтаксисом какого либо языка. По умолчанию, язык определяется автоматически, для того, чтобы задать конкретный язык необходимо выставить атрибут class='prettyprint-lang', где вместо lang язык из списка (bsh, c, cc, cpp, cs, csh, cyc, cv, htm, html, java, js, m, mxml, perl, pl, pm, py, rb, sh, xhtml, xml, xsl)

Второй способ - с помощью сайтов постеров кода, как пример - quick highlighter. Все что нужно - запостить свой код и получить готовый отформарматированный html-код. На выше упомянутом сайте есть два способа генерации html-кода - отдельно css и html (css нужно преварительно поместить в хеадеры) и все вместе(все в html-коде). На примере blogger.com для добавления css в хеадеры нужно опять же подправить шаблон страницы, для этого Layout->Edit HTML.

10 апр. 2009 г.

Hibernate. Интернационализация БД. О том как плохо получилось.

Снова пятница (не тяпница, по тяпницам у меня хорошее настроение), на улице дождь, настроение в ноль. Целую неделю прихожу на работу на час раньше, ну не дурень разве?)
В добавок ко всему больной... Ну да ладно о плохом, раскажу ка я лучше о своей очередной бредовой идее.
Проснулся как то ночью и решил, хочу чтобы мои справочники в БД были локализованы... Начал рыться по нету, что да как... Суть в том, что есть какие то сущности замапленые с помощью hibernate на реляционную БД, у этих сущностей есть нейкий description, который и нужно локализировать.

Так вот, подумал немного и кое как с горем попалам придумал кое чего, хотя мне кажется, что уж слишком грязно все получилось. Ну да ладно, как бы не было стыдно, покажу то, что у меня получилось.
Для начала сделал вот такой интерфейсик, для интернационализации чего-то, и 2 его реализации
package itdep.common.i18n;

import java.util.Locale;

/**
* @author Ivan Khalopik
* @version $Revision: 267 $ $Date: 2009-04-09 09:38:38 +0300 (Чт, 09 апр 2009) $
*/
public interface I18n<T> {

  T getValue(final Locale locale);

  void setValue(final Locale locale, final T value);

}

package itdep.common.i18n.impl;

import itdep.common.i18n.I18n;

import java.util.Locale;

/**
 * @author Ivan Khalopik
 * @version $Revision: 267 $ $Date: 2009-04-09 09:38:38 +0300 (Чт, 09 апр 2009) $
 */
public class SimpleI18n<T> implements I18n<T> {
  private T value;

  public SimpleI18n() {
  }

  public SimpleI18n(final T value) {
    this.value = value;
  }

  public T getValue(final Locale locale) {
    return value;
  }

  public void setValue(final Locale locale, final T value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }
}

package itdep.common.i18n.impl;

import itdep.common.i18n.I18n;
import itdep.common.util.I18nUtils;

import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * @author Ivan Khalopik
 * @version $Revision: 268 $ $Date: 2009-04-09 11:55:04 +0300 (Чт, 09 апр 2009) $
 */
public class I18nMapWrapper<T> implements I18n<T> {
  private final Map<Locale, T> values;

  public I18nMapWrapper(final Map<Locale, T> values) {
    this.values = values;
  }

  public T getValue(final Locale locale) {
    final List<Locale> candidates = I18nUtils.getCandidateLocales(locale);
    for (Locale candidate : candidates) {
      if (values.containsKey(candidate)) {
        return values.get(candidate);
      }
    }
    return null;
  }

  public void setValue(final Locale locale, final T value) {
    values.put(locale, value);
  }

  @Override
  public String toString() {
    return String.valueOf(getValue(Locale.getDefault()));
  }
}

Первая реализация - это что вроде заглушки, когда объект во всех локалях имеет одно и то же значение, во вторую подсовываем мапу с ключем в виде локали, из которой в соответсвии с нужной локалью и будет браться локализированое значение.
Далее все это нужно совместить с hibernate сущностью. Для этого объявил интерфейс:<>
package itdep.common.i18n;

import itdep.common.dao.Entity;

import java.io.Serializable;

/**
 * @author Ivan Khalopik
 * @version $Revision: 265 $ $Date: 2009-04-08 11:37:12 +0300 (Ср, 08 апр 2009) $
 */
public interface I15dEntity<PK extends Serializable> extends Entity<PK> {

  I18n<String> getDescription();

}

Ну и реализация
package itdep.model;

import itdep.common.i18n.I18n;
import itdep.common.i18n.impl.I18nMapWrapper;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.MapKey;

import javax.persistence.*;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * @author Ivan Khalopik
 * @version $Revision: 265 $ $Date: 2009-04-08 11:37:12 +0300 (Ср, 08 апр 2009) $
 */
@MappedSuperclass
public abstract class CodedEntity extends SomeEntity implements I15dEntity<Long> {

  @Column(name = "CODE", nullable = true, insertable = true, updatable = true)
  private String code;

  @CollectionOfElements(fetch = FetchType.EAGER)
  @JoinTable(name = "I18N",
    joinColumns = @JoinColumn(name = "CODE", referencedColumnName = "CODE", nullable = false))
  @MapKey(columns = @Column(name = "LOCALE", nullable = true, insertable = true, updatable = true))
  @Column(name = "VALUE", nullable = false, insertable = true, updatable = true)
  private Map<Locale, String> i15dName = new HashMap<Locale, String>();

  public String getCode() {
    return code;
  }

  public void setCode(final String code) {
    this.code = code;
  }

  @Override
  public I18n<String> getDescription() {
    return new I18nMapWrapper<String>(i15dName);
  }
}

Здесь для локализации используется таблица I18N с полями CODE(код), LOCALE(локаль), VALUE(значение), в самой таблице сущности храним поле CODE, которое и соотетсвует тому, что в I18N. В итоге получаем, что #getDescription() возвращает нам локализированую строку. Ну а в случае, если надо, чтоб возвращалось одно и то же значение во всех локалях можно сделать так:
package itdep.model;

import itdep.common.i18n.I18n;
import itdep.common.i18n.impl.SimpleI18n;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;

/**
 * @author Ivan Khalopik
 * @version $Revision: 265 $ $Date: 2009-04-08 11:37:12 +0300 (Ср, 08 апр 2009) $
 */
@MappedSuperclass
public abstract class NamedEntity extends SomeEntity implements I15dEntity<Long> {

  @SuppressWarnings({"UnusedDeclaration"})
  @Column(name = "NAME", nullable = false, insertable = true, updatable = true)
  private String name;

  @Override
  public I18n<String> getDescription() {
    return new SimpleI18n<String>(name);
  }
}

Ну вот что-то и получилось, но мне не нравиться, буду видимо переделывать, хочу реализовать отображение через спринговый MessageSource, а редактирование можно и так, немного переделать, или, возможно, что-то совсем по другому сделать. Вобщем продолжение следует.
Все, пойду лечиться...

3 апр. 2009 г.

TestNG и о том как привести результаты к совместимым с JUnit

Опять тяпница, опять хорошее предпразничное настроение и опять мой мозг разрывается в выборе из той кучи бестолковых идей, которые в нем витают...

Однажды переводил тесты из JUnit на TestNG и столкнулся с одной маленькой проблеммкой. А проблеммка эта заключается в том, что CruiseControl нивкакую не соглашается понимать результаты тестов от TestNG, даже за пиво :)


Покопался в инете, нашел одну вот такую вещь:
TestNG contains a listener that takes the TestNG results and outputs an XML file that can then be fed to JUnitReport.

В общем все решается просто - одним антовским таксом:

<target name="reports">
<junitreport todir="test-report">
<fileset dir="test-output">
<include name="**/*.xml"/>
</fileset>
<report format="noframes" todir="test-report"/>
</junitreport>
</target>

Но здесь есть еще одно но. Мой проект не использует ant, все базируется на maven, и мне бы очень не хотелось добавлять в него еще и антовский таск. На помощь мне в сложившейся ситтуации пришел плагин maven-antrun-plugin. Все что нужно, это переместить антовский таск в мавеноский pom.xml и делается это следующим образом:


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<configuration>
<tasks>
<junitreport todir="${project.build.directory}/surefire-reports">
<fileset dir="${project.build.directory}/surefire-reports">
<include name="**/*.xml"/>
</fileset>
<report format="noframes" todir="${project.build.directory}/surefire-reports"/>
</junitreport>
</tasks>
</configuration>
</plugin>

Теперь запускаем antrun:run и... все готово. Тесты сконвертились, CruiseControl доволен, а мы идем пить тяпничное пиво.