barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

пятница, 28 сентября 2012 г.

PostgreSQL: ERROR: language "plpgsql" does not exist

Ошибка:
ERROR: language "plpgsql" does not exist
решается выполнением запроса:
CREATE LANGUAGE plpgsql
(запрос создает язык в масштабе БД, поэтому для каждой БД должен выполняться отдельно) 

четверг, 27 сентября 2012 г.

Запуск bat-ника из Java

Runtime.getRuntime().exec("cmd /c start my.bat");

Уменьшение размера war-файлов моков веб-сервисов soapUI

У war-файлов с моками веб-сервисов, которые генерирует soapUI, есть одна существенная проблема - они очень большие (около 68Мб). Особенно эта проблема ощущается, когда нужно развернуть много моков сразу, а канал до сервера очень узкий. 
99% размера мока занимают библиотеки, которые у всех моков одинаковые. Поэтому первая идея, которая возникла - положить все эти библиотеки в /lib томката, на котором производится развертывание. Тогда в war-никах перед их деплоем можно удалять все jar-файлы из WEB-INF/lib (не руками конечно, написать для этого ant-билдфайл не проблема, тем более, что он позволит сразу и автоматизировать развертывание, чтобы не делать это через веб-интерфейс Tomcat Manager`а).
Но тут возникла следующая курьезная ситуация: мок, развернутый последним, каким-то чудесным образом заменял конфигурацию всех ранее развернутых моков на свою, и в результате все мок-сервисы на томкате начинали работать как один - выдавать одинаковые wsdl`ки и т.п. Конечно, такому поведению можно найти логичное объяснение, но лучше от этого не становится =)
Поэтому пришлось поступить хитрее. Во-первых, я переместил все soapui-шные библиотеки из /lib томката   в другое место (не суть важно, в какое). Во-вторых, процесс деплоя стал выглядеть так: 
  1. Мок деплоится без библиотек
  2. Только что развернутое веб-приложение останавливается
  3. В его WEB-INF/lib делаются симлинки на все необходимые библиотеки (чтобы не копировать и не занимать тем самым лишнее место на диске сервера)
  4. Приложение стартует
Для автоматизации такого процесса одним только ant-билдфайлом на стороне клиента (машины, с которой производится развертывание) не обойтись (если только поднять ssh / ftp на сервере, что мне показалось не лучшей идеей), пришлось на сервере разместить небольшой сервлет, выполняющий создание симлинков и вызываемый из клиентского билдфайла. Всё, задача решена, моки размером в несколько десятков килобайт деплоятся влет.

Birt: форматирование дат

Чтобы вывести в поле "Dynamic Text" дату в определенном формате, можно воспользоваться функцией Formatter.format(). Например, такой код выведет отформатированные даты из двух параметров отчета в формате "дд.мм.гггг":
"Количество запросов в период с "
+Formatter.format( params["periodStart"].value,  "dd.MM.yyyy")
+ " по "
+Formatter.format( params["periodEnd"].value,  "dd.MM.yyyy")
Для форматирования дат в полях "Text" можно воспользоваться тегом <value-of/>. Например, вывод текущей даты в томже формате "дд.мм.гггг" выглядит так:
<value-of format="dd.MM.yyyy">new Date()</value-of>
ЗЫ Спасибо http://birtworld.blogspot.com/2011/02/birt-formatting-numbers-and-dates.html

Birt: как сделать, чтобы заголовок таблицы не повторялся на каждой странице отчета

Бывает, особенно если заголовок состоит из нескольких строк, необходимо, чтобы строка заголовка не повторялась на каждой странице отчета, а отражалась только на первой. Для этого нужно выделить соответствующую строку заголовку и в свойствах установить Repeteable=false:



или в XML-виде установить соответствующее свойство:
<row id="...">
     <property name="repeatable">false</property>
     <cell id="...">

среда, 26 сентября 2012 г.

BIRT Report Viewer 4.2 на Tomcat 6: проблема с default workspace и объявлениями JDBC драйверов

Проблема:

На Tomcat 6 развернут BIRT Report Viewer 4.2 (просто взят и развернут WAR-ник из Birt Runtime). Однако при попытке просмотреть какой-либо отчет в интерфейс выдается невнятная ошибка:
Can not load the report query
, а в лог logs/ReportEngine_xxx.log, расположенный в корне веб-приложения, валится исключение:
SEVERE: Unable to open connection.
org.eclipse.datatools.connectivity.oda.OdaException ;
    java.lang.IllegalStateException: Unable to determine the default workspace location.  Check your OSGi-less platform configuration of the plugin or datatools workspace path.
...
Причина:

BIRT`у необходимо указать путь к default workspace`у, без него он работать не может (в простейшем случае workspace - просто пустая папка). Сделать это можно либо через переменную окружения, либо через системное свойство, второй способ, как по мне, более логичен. Для этого в init.d-скрипт томката (/etc/init.d/tomcat6) я добавил нужную опцию к уже имевшимся там ранее:
JAVA_OPTS="-Djava.awt.headless=true -Xmx256M -XX:MaxPermSize=512m -XX:PermSize=128m -XX:+UseConcMarkSweepGC
-Dorg.eclipse.datatools_workspacepath=$CATALINA_BASE/workspace_dtp"
Папку $CATALINA_BASE/workspace_dtp необходимо создать и убедиться, что у томката будут права на запись в неё.
Кстати, эта директория пригодится, чтобы, например, положить в неё файл с определениями JDBC-драйверов, без которых отчеты, использующие соединения с БД, будут валится с исключениями:
SEVERE: Unable to open connection.
org.eclipse.datatools.connectivity.oda.OdaException ;
    java.lang.Exception: Driver definition could not be found.
...
Caused by: java.lang.Exception: Driver definition could not be found.
...
Caused by: java.lang.Throwable: DriverConnectionBase.error.driverDefinitionNotFound
...
, даже если в самом отчете в <property name="jarList"> указан путь к jar-нику. Файл определения JDBC-драйверов называется driverStorage.xml и располагается в подпапке org.eclipse.datatools.connectivity. Его можно скопировать с машины, на которой производится разработка отчетов, исправив пути к jar-никам драйверов (записи о ненужных драйверах можно удалить). Вот пример содержимого этого файла для одного единственного postgresql-драйвера:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataTools.PropertySets>
<propertySet iD="DriverDefn.org.eclipse.datatools.enablement.postgresql.postgresqlDriverTemplate.PostgreSQL JDBC Driver" keys="org.eclipse.datatools.connectivity.drivers.defnType jarList org.eclipse.datatools.connectivity.db.username org.eclipse.datatools.connectivity.db.driverClass org.eclipse.datatools.connectivity.db.databaseName org.eclipse.datatools.connectivity.db.password org.eclipse.datatools.connectivity.db.URL org.eclipse.datatools.connectivity.db.version org.eclipse.datatools.connectivity.db.vendor " name="PostgreSQL JDBC Driver" prop_jarList="/var/lib/tomcat6/workspace_dtp/postgresql-8.4-701.jdbc4.jar" prop_org.eclipse.datatools.connectivity.db.URL="jdbc:postgresql://localhost:5432/database" prop_org.eclipse.datatools.connectivity.db.databaseName="database" prop_org.eclipse.datatools.connectivity.db.driverClass="org.postgresql.Driver" prop_org.eclipse.datatools.connectivity.db.password="" prop_org.eclipse.datatools.connectivity.db.username="pg" prop_org.eclipse.datatools.connectivity.db.vendor="postgres" prop_org.eclipse.datatools.connectivity.db.version="8.x" prop_org.eclipse.datatools.connectivity.drivers.defnType="org.eclipse.datatools.enablement.postgresql.postgresqlDriverTemplate"/>
</DataTools.PropertySets>

вторник, 25 сентября 2012 г.

Birt: настройка границ ячеек таблицы

По-умолчанию таблицы отрисовываются вообще без границ ячеек, что вряд ли является удачным решением. В тоже время настроить границы ячеек можно не вполне очевидным образом:
  1. Наводи мышь на таблицу, чтобы появился ярлычок "Table" под её нижним левым углом
  2. Щелкаем по ярлычку:
  3. Щелкаем по верхнему левому углу появившейся рамки вокруг таблицы (вокруг таблицы появится синяя рамка):
  4. В свойствах настраиваем границы:

понедельник, 24 сентября 2012 г.

Apache2 на CentOS: mod_proxy и ошибка "Permission denied: proxy: HTTP: attempt to connect to ... (*) failed"

Проблема:

При попытке проксировать запросы через Apache2 куда-либо (хоть на localhost, хоть на другой сервер), получаем ошибку 503 и сообщение в логах:
[error] (13)Permission denied: proxy: HTTP: attempt to connect to ... (*) failed
Причина:

SELinux блокирует исходящие подключения httpd.

Решение:

Разблокировать командой (эффект сохранится после перезагрузки):
 # setsebool -P httpd_can_network_connect 1

пятница, 21 сентября 2012 г.

away3d 4: виджет статистики

Раньше (в away3d 3.5) виджет статистики не требовал программного добавления и был доступен в контекстном меню при клике на 3D-объекте. Теперь он добавляется программно:
import away3d.Away3D;
...
addChild(AwayStats.instance);
Как и раньше, он содержит сведения о FPS, использовании памяти и пр.:


Чтобы увидеть статистику по числу полигонов и драйверу, необходимо зарегистрировать все используемые View3D (у меня, например, из два):
AwayStats.instance.registerView(_leftView);
AwayStats.instance.registerView(_rightView);
Теперь статистика выглядит так:


вторник, 18 сентября 2012 г.

Netbeans 7.2 в Win 7: где запрятали ant?

Раньше (не помню правда, в какой версии) ant лежал в папке установки Netbeans (в подпапке вроде java/ant), теперь же он размещается тут:
%USERPROFILE%\AppData\Roaming\NetBeans\7.2\ant\
Чтобы узнать это, пришлось воспользоваться крайней полезным в некоторых ситуациях ant-заданием "diagnostics".
Соответственно, если есть необходимость подложить в ant jar-ники со своими заданиями (чтобы не привязываться в абсолютным / относительным путям в <taskdef/>), то класть их нужно в:
%USERPROFILE%\AppData\Roaming\NetBeans\7.2\ant\lib

понедельник, 17 сентября 2012 г.

JAX-WS: IllegalAnnotationsException "Two classes have the same XML type name" на ровном месте

Проблема:

Есть веб-сервис, построенный на JAX-WS, у которого есть веб-метод, реализующий soap-операцию "op", тип его возврата - OpResponse: 
@WebService(serviceName = " MyService ")
public class MyService
{
...
@WebMethod(operationName = "op")
public OpResponse op(...) {...}
...
}
...
public class OpResponse { ... }
При развертывании этого веб-сервиса валится ошибка:
com.sun.xml.ws.transport.http.servlet.WSServletException: WSSERVLET11: failed to parse runtime descriptor: javax.xml.ws.WebServiceException: Unable to create JAXBContext
...
Caused by: javax.xml.ws.WebServiceException: Unable to create JAXBContext
...
Caused by: java.security.PrivilegedActionException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Two classes have the same XML type name "{...}opResponse". Use @XmlType.name and @XmlType.namespace to assign different names to them.
Хотя вроде бы никаких двух классов с одинаковым XML-типом в проекте нет (впрочем, XML-типы пока явно вообще нигде не фигурируют).

Причина:

Причину ошибки можно объяснить с двух сторон - с точки зрения классов или с точки зрения генерируемых jaxws`ом WSDL/XSD. Я приведу второе объяснение.
Генерируя WSDL, JAX-WS использует имя "opResponse" как имя XSD-типа ответа операции "op" (т.е. он просто конкатенирует имя операции и "Response"), который потом уже, в XSD-схеме, раскрывается с использованием XSD-типа, соответствующего возвращаемому Java-классу:
<operation name="op">
<input wsam:Action="http://.../opRequest" message="tns:op"/>
<output wsam:Action="http://.../opResponse" message="tns:opResponse"/>
</operation>
<xs:complexType name="opResponse">
<xs:sequence>
<xs:element name="return" type="tns:<responseClassName>" minOccurs="0"/>
</xs:sequence>
</xs:complexType> 
Здесь <responseClassName> - это имя класса, который является типом возврата метода "op", написанное со строчной первой буквы. В нашем случае это имя должно было бы быть равным снова "opResponse", т.к. тип возврата метода "op" - именно класс OpResponse, что сделало бы XSD-схему некорректной.

Решение:

Аннотировать класс OpResponse с помощью аннотации @XmlType, как и советует JAX-WS, так, чтобы имя XSD-типа, соответствующего классу OpReponse, отличалось от "opResponse", например:
@XmlType(name = "OpResponseType")
public class OpResponse {...}
Тогда схема примет вид:
...
<xs:complexType name="opResponse">
 <xs:sequence>
  <xs:element name="return" type="tns:OpResponseType" minOccurs="0"/>
 </xs:sequence>
</xs:complexType>
...
и веб-сервис стартанет без проблем. 

суббота, 15 сентября 2012 г.

Проект away3d 4.0 в Flash Builder 4.6: пустой экран и ошибка "Класс Context3D недоступен"

Проблема:

Создал ActionScript-проект в Flash Builder 4.6, подключил сорцы away3d 4.0, но при запуске даже элементарных примеров с away3d.com получаю пустой экран и ошибку (если установлен отладочный Flash Player):
Error #2044: Необработанный ErrorEvent:. text=Error #3702: Класс Context3D недоступен.
Решение:

Открыть html-шаблон, который используется для запуска проекта, и установить в нем параметр wmode равным "direct" в JavaScript-коде создания swf-объекта:

<script type="text/javascript">
            // For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection.
            var swfVersionStr = "${version_major}.${version_minor}.${version_revision}";
            // To use express install, set to playerProductInstall.swf, otherwise the empty string.
            var xiSwfUrlStr = "${expressInstallSwf}";
            var flashvars = {};
            var params = {wmode: "direct"};
            params.quality = "high";
            params.bgcolor = "${bgcolor}";
            params.allowscriptaccess = "sameDomain";
            params.allowfullscreen = "true";
            var attributes = {};
            attributes.id = "${application}";
            attributes.name = "${application}";
            attributes.align = "middle";
            swfobject.embedSWF(
                "${swf}.swf", "flashContent",
                "${width}", "${height}",
                swfVersionStr, xiSwfUrlStr,
                flashvars, params, attributes);
            // JavaScript enabled so display the flashContent div in case it is not replaced with a swf object.
            swfobject.createCSS("#flashContent", "display:block;text-align:left;");
        </script>
Это включит аппаратное 3D-ускорение. Предыдущие версии библиотеки работали с другими wmode`ами (transparent, opaque), что, конечно, давало проигрыш в производительности, но всё же. У wmode=direct есть одна особенность: объекты View3D всегда располагаются под остальным 2D-контентом, независимо от порядка их добавления в контейнер вызовом addChild и от индекса при добавлении с помощью addChildAt. Так что нужно быть осторожным, чтобы, например, случайно не залить 3d-сцену поверх как-нибудь заливкой.

пятница, 14 сентября 2012 г.

Orbeon: рестилизация печатной формы

При генерации pdf по форме (без использования собственного pdf-шаблона формы) Orbeon корректно обрабатывает правило @media, поэтому управлять стилем генерируемой pdf-ки можно с помощью css, устанавливая различные стили для @media screen и @media print.

четверг, 13 сентября 2012 г.

Netbeans 7.2: перемещение проектов между группами проектов

Группы проектов - отличная идея для IDE, но в NetBeans реализация пока что сыровата. Например, для того, чтобы переместить проект из одной группы в другую нужно его закрыть, переключиться в необходимую группу, и открыть его, находясь в ней. 
Переключение между группами также далеко от идеала юзабилити - только через контекстное меню окна "Проекты".

понедельник, 10 сентября 2012 г.

Xalan: двойной CR в CDATA-секциях

Наткнулся на баг в библиотеке Xalan (являющейся в моем случае реализацией по-умолчанию javax.xml.transform.TransformerFactory): при сериализации в Windows XML-документа с CDATA-секциями переводы строки в этих секциях представляются последовательностями CR-CR-LF вместо CR-LF. В баг трекере апача соответствующая заявка лежит уже давным-давно без ответа: https://issues.apache.org/jira/browse/XALANJ-2547. Выход - например, заменять вручную в полученной строке "\r\r\n" на "\r\n":

public static String domToString(Document dom) throws TransformerException
{
Source source = new DOMSource(dom);
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return stringWriter.getBuffer().toString().replace("\r\r\n", "\r\n");

пятница, 7 сентября 2012 г.

Ant: выполнение действий над каждым файлом из набора

Задача:

В билдфайле ant над каждым файлом из набора (заданного с помощью <fileset/>) выполнить некоторое действие или последовательность действий.

Решение:

Один из вариантов -  воспользоваться библиотекой Ant-Contrib (скачать её, а также почитать документацию можно тут: http://ant-contrib.sourceforge.net/, я использовал версию 0.6). Она предоставляет задачу <foreach/>, позволяющую задать набор файлов вложенным элементом <path/> и вызвать для каждого файла указанную цель, передав ей имя файла в качестве параметра. Вот небольшой пример:
<taskdef name="foreach" classname="net.sf.antcontrib.logic.ForEach"
             classpath="path/to/ant-contrib-0.6.jar"/>

<target ...>
<foreach target="mytarget" param="myfile">
<path>
<fileset dir="${basedir}">
<include name="*.jar"/>
</fileset>    
</path>
</foreach>
</target>

<target name="mytarget">
<echo message="filename is ${myfile}" />
</target>
Здесь foreach проходит по всем jar-файлам из базовой директории, и для каждого вызывает цель "mytarget", передавая ей абсолютный путь к файлу через параметр "myfile".

PS В принципе, в ant есть встроенная задача <apply/>, но она умеет выполнять для каждого файла из набора только внешние команды, а не внутренние цели.

четверг, 6 сентября 2012 г.

Debian Lenny: куда делись репозитории

Lenny это конечно архаизм, но всё же. 

Проблема:

При попытке выполнить apt-get update стали валиться ошибки 404 и 301. 

Причина:

Репозитории переехали в архив.

Решение:

Задать в /etc/apt/sources.list новые URL`ы репозиториев:
deb http://archive.debian.org/debian/ lenny main contrib non-free
deb-src http://archive.debian.org/debian/ lenny main contrib non-free

PostgreSQL: получение числа секунд, прошедших с 1970-01-01, из timestamp-поля

SELECT extract(epoch from <timestamp_column>) FROM ...
Тип возвращаемого значения - double precision. 

вторник, 4 сентября 2012 г.

jTrust: OnlineCrlRepository и прокси с авторизацией

Онлайн-репозиторий CRL из библиотеки jTrust реализует загрузку CRL-файлов по указанным URL`ам точек распространения сертификатов (CRL Distribution Points). Поддержка прокси в нем есть, и реализуется она передачей объекта NetworkConfig в конструктор класса:
import be.fedict.trust.NetworkConfig;
import be.fedict.trust.crl.OnlineCrlRepository;
// ...
NetworkConfig nwcfg = new NetworkConfig(proxyHost, proxyPort);
OnlineCrlRepository onlineCrlRepo = new OnlineCrlRepository(nwcfg); 
Но такой вариант работает только с прокси-серверами без авторизации.
В тоже время Apache HttpClient, используемый классом OnlineCrlRepository для загрузки файла CRL, авторизацию на прокси поддерживает (не уверен, что все возможные варианты авторизации реализованы, но по крайней мере BASIC есть): http://hc.apache.org/httpclient-3.x/authentication.html#Proxy_Authentication. Но т.к. внутренние поля и методы OnlineCrlRepository являются закрытыми, наследовать его для добавления нового функционала нет особого смысла, приходится копировать исходники в свой класс, добавляя возможность установки авторизации на прокси. 
Для этого я, во-первых, создал специальный класс ProxyCredentials, унаследовав be.fedict.trust.Credentials, предназначенный для хранения данных http-авторизации. Класс  be.fedict.trust.Credentials содержит метод init(), который применяет авторизационные данные к объекту HttpState. Этот метод я и переопределил таким образом, чтобы он задавал не данные авторизации на конечном сайте, а данные авторизации на прокси, заменив вызов httpState.setCredentials() на httpState.setProxyCredentials():
public class ProxyCredentials extends be.fedict.trust.Credentials
{
  @Override
  public void init(HttpState httpState) {
      for (Credential credential : this.getCredentials()) {
        AuthScope authScope = new AuthScope(credential.getHost(),
            credential.getPort(), credential.getRealm(),
            credential.getScheme());
        UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(
            credential.getUsername(), credential.getPassword());
        httpState.setProxyCredentials(authScope, usernamePasswordCredentials);
      }
    }
}
Теперь достаточно в нашем варианте класса OnlineCrlRepository добавить поле для хранения объекта ProxyCredentials, setter-метод для этого поля, а также добавить в метод getCrl() применение этих авторизационных данных к объекту HttpState:
public class AdvancedOnlineCrlRepository implements CrlRepository {

  // ..
  protected ProxyCredentials proxyCredentials;
  /**
   * Sets the credentials for proxy authentication
   * @param proxyCredentials
   */
  public void setProxyCredentials(ProxyCredentials proxyCredentials) {
    this.proxyCredentials = proxyCredentials;
  }

  // ..

protected X509CRL getCrl(URI crlUri) throws IOException,
CertificateException, CRLException, NoSuchProviderException,
NoSuchParserException, StreamParsingException {
HttpClient httpClient = new HttpClient();
if (null != this.networkConfig) {
httpClient.getHostConfiguration().setProxy(
this.networkConfig.getProxyHost(),
this.networkConfig.getProxyPort());
}
if (null != this.credentials) {
HttpState httpState = httpClient.getState();
this.credentials.init(httpState);
}
 
if (null != this.proxyCredentials) {
HttpState httpState = httpClient.getState();
this.proxyCredentials.init(httpState);
}
 
String downloadUrl = crlUri.toURL().toString();
//...
return crl;
}
}
Теперь создание объект репозитория CRL для работы с прокси-сервером с авторизацией выглядит так:
NetworkConfig nwcfg = new NetworkConfig(proxyHost, proxyPort);
ProxyCredentials creds = new ProxyCredentials();
Credential cred = new Credential(proxyHost, proxyPort, proxyLogin, proxyPassword);
creds.addCredential(cred);
AdvancedOnlineCrlRepository onlineCrlRepo = new AdvancedOnlineCrlRepository(nwcfg);
onlineCrlRepo.setProxyCredentials(creds);