barbitoff programmer`s blog

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

понедельник, 31 декабря 2012 г.

Java: получение вывода, выданного запущенным bat-ником

Уже как-то писал, как из Java вызывать bat-ники, теперь расскажу, как получить после этого то, что bat-ник вывел в "консоль".
Делается это так:
Process proc = Runtime.getRuntime().exec("cmd /c start my.bat");
proc.waitFor();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(proc.getInputStream(), bos);
String batOutput = new String(bos.toByteArray(),"Cp866");
Здесь используется класс IOUtils из commons-io. Результат получаем в переменной  batOutput.

WSO2 ESB: как заставить прокси сервис использовать SOAP 1.1 в исходящих сообщениях

Проблема:

Есть прокси-сервис на WSO2 ESB 4.5.1, вызывающий конечный веб-сервис по http. Конечный сервис реализован на JAX-WS и использует версию SOAP 1.1. При попытке использовать SOAP 1.2 (используемый шиной по-умолчанию) валится с исключением:
com.sun.xml.ws.server.UnsupportedMediaException: Unsupported Content-Type: application/soap+xml; charset=UTF-8; action="xxx" Supported ones are: [text/xml]
Необходимо заставить прокси-сервис WSO2 использовать именно версию 1.1.

Решение:

Добавить атрибут format="soap11" в настройку конечной точки на шине:
<endpoint xmlns="http://ws.apache.org/ns/synapse" name="[EP_NAME]">
    <address format="soap11" statistics="disable" trace="disable" uri="[EP_URI]">
...

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

maven-war-plugin: исключение файлов из WAR-ника

Исключение файлов из собираемого WAR-ника выполняется с помощью тега <packagingExcludes/> (ниже приведен пример исключения log4j.properties):

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<packagingExcludes>WEB-INF/classes/log4j.properties</packagingExcludes>
</configuration>
</plugin>

WSO2 ESB: быстрый trace содержимого сообщения в консоль

Конечно, для трэйса можно использовать log-медиатор, но если нужно всего лишь вывалить содержимое envelope`а в консоль, можно воспользоваться script-медиатором:
<script language="js"><![CDATA[
print(mc.getEnvelope());
]]></script>

com.sun.istack.internal.SAXException2: unable to marshal type as an element because it is not known to this context

Проблема:

Есть набор классов, сгенерированных wsimport`ом. Расположены эти классы в пакете "my.package". В проекте в этот же пакет добавлен ещё один класс "MyExtraClass", и есть желание выполнить его маршаллизацию:
MyExtraClass obj = new MyExtraClass();
...
JAXBContext jc = JAXBContext.newInstance("my.package");
Marshaller marshaller = jc.createMarshaller();
StringWriter xmlWriter = new StringWriter();
marshaller.marshal(obj, xmlWriter); 
Однако, последний вызов валится с исключением:
com.sun.istack.internal.SAXException2: unable to marshal type "my.package.MyExtraClass" as an element because it is not known to this context
Т.е., по-видимому, JAXB этого класса по какой-то причине не видит.

Причина:

wsimport, помимо классов, соответствующих XML-типам, сгенерировал в пакете my.package также класс ObjectFactory, определяющий, какие классы будут известны JAXB-контексту. Естественно, эта ObjectFactory о новом классе MyExtraClass ничего не знает, поэтому ничего о нем не знает и JAXB-контекст.

Решение:

Т.к. ObjectFactory менять не хочется, он ведь генерируется при компиляции автоматически, нужно добавить наш MyExtraClass в JAXB-контекст каким-то другим способом. Для этого подойдет такой вариант:
  1. Создать в пакете "my.package" файл "jaxb.index"
  2. Записать в этот файл название нашего класса, без указания пакета, т.е. просто:
    MyExtraClass
Все, теперь класс будет маршаллизироваться без проблем.

DQL: использование объединений типов и повторяющихся атрибутов в запросе

Если просто попробовать использовать в запросе несколько типов в объединении и выбирать при этом какой-нибудь повторяющийся атрибут (например, тот же "i_folder_id"), будет валится ошибка:
[DM_QUERY2_E_REPEAT_TYPE_JOIN]error: "Your query is selecting repeating attributes and joining types."
Выход - добавить в конец запроса "ENABLE (ROW_BASED)", тогда для каждого значения повторяющегося атрибута будет возвращена отдельная строка, и запрос выполнится без ошибок (но приведет, возможно, к дублированию одих и тех же объектов в разных строках).

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

JAX-WS: обертка ответов от разных веб-методов в одинаковые теги

Задача:

В JAX-WS-веб-сервисе (используется имплементация JAX-WS RI)  есть несколько методов, возвращающих объекты одного типа (пусть это будет класс OperResultClass):
@WebMethod(operationName = "operationA")
public OperResultClass operationA(){...}
@WebMethod(operationName = "operationB")
public OperResultClass operationB(){...}
@WebMethod(operationName = "operationC")
public OperResultClass operationC(){...}...
Необходимо, чтобы XML, получаемый на их выходе, выглядел так:
<MyResponseWrapperTag xmlns="http://soap.response">
<MyResponseTag>
<!-- содержимое объекта-ответа -->
</MyResponseTag>
</MyResponseWrapperTag>
Т.е. необходимо задать для всех методов:
  1. Имя наружного тега и его пространство имен
  2. Имя внутреннего тега и его пространство имен

Решение:

Второй пункт делается просто: каждый метод аннотируется следующим образом:
@WebResult(name="MyResponseTag", targetNamespace="http://soap.response")
После этого мы будем иметь правильный внутренний тег, а вот наружный будет генерироваться JAX-WS`ом автоматически, основываясь на имени метода (он будет состоять из имени метода + "Response").

Для задания имени наружного тега используется аннотация @ResponseWrapper. Но использовать её так:
  @ResponseWrapper(
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
не получится - будет валиться исключение:
Two classes have the same XML type name "{http://soap.response}MyResponseWrapperTag". Use @XmlType.name and @XmlType.namespace to assign different names to them.
Тут дело в том, что для оберток вокруг ответа JAX-WS генерирует свои классы, по одному на каждый метод. Т.о, для каждого веб-метода будет использоваться свой класс, в то время как из-за @ResponseWrapper всем им будет назначен одно и тот же имя XML-типа. 
Выход из ситуации - самостоятельно задавать имя класса-обертки, чтобы JAX-WS не генерировал своих классов. Делается это следующим образом:
@ResponseWrapper(          className="my.MyResponseWrapper",
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
Теперь нужно создать этот самый класс-wrapper "my.MyResponseWrapper". Он будет выглядеть примерно так:
@XmlType(name="MyResponseWrapperType",namespace="http://soap.response")
@XmlAccessorType(XmlAccessType.FIELD)
public class MyResponseWrapper{
  @XmlElement(name="MyResponseTag",namespace="http://soap.response")
  public OperResultClass MyResponseTag;
}
Здесь важно, чтобы параметры аннотации @XmlElement совпадали с таковыми в аннотации @WebResult метода. Namespace можно не указывать, если он задан на уровне пакета в package-info.java:
@XmlSchema(namespace = "http://soap.response", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package my;
import javax.xml.bind.annotation.XmlSchema;



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

javax.xml.ws.spi.FactoryFinder$ConfigurationError

Проблема:

При попытке в приложении создать объект клиента к веб-сервису, построенного на JAX-WS, валится исключение:
javax.xml.ws.spi.FactoryFinder$ConfigurationError: Provider org.apache.cxf.jaxws.spi.ProviderImpl not found
Причина:

Где-то в CLASSPATH переопределен JAX-WS провайдер на Apache Cxf, при этом самого Cxf в CLASSPATH нет (ну или, если используется OSGI, он может быть просто не импортирован).

Решение:

Возможных решения, в общем-то, 3:
  • Найти и положить в CLASSPATH / импортировать бандл Cxf
  • Найти, где в CLASSPATH переопределен JAX-WS-провайдер и удалить переопределение. Переопределяется он файлом META-INF/services/javax.xml.ws.spi.Provider, в который записано имя класса провайдера.
  • Переопределить провайдер обратно в своем приложении так, чтобы Ваш файл лежал в CLASSPATH ближе, чем вражеский. В случае с собираемым Maven`ом приложением нужно создать файл src/main/resources/META-INF/services/javax.xml.ws.spi.Provider, и записать в него (если Вы хотите вернутся к провайдеру по-умочанию, который идет с JDK): 
    com.sun.xml.internal.ws.spi.ProviderImpl


MS Office 2010: неработающие Ctrl-комбинации

Неожиданно, как всегда в самый неподходящий момент, перестали работать все Crtl-комбинации в MS Office. Не знаю, как вернуть все обратно одним нажатием, но вручную переназначить все комбинации клавиш обратно можно так:
  1. File->Options->Customize Ribbon -> Keyboard shortcuts (внизу) -> Customize 
  2. Выбираем All Commands в левом списке, а затем в правом, последовательно, FileSave, EditCut, EditCopy, EditPaste и т.п. по всем нужным комбинациям, для каждой вводим Ctrl-комбинацию в поле "Press new shortcut key", после чего жмем Assign.

воскресенье, 16 декабря 2012 г.

jQuery: сериализация объекта / массива в строку GET-запроса

Для сериализации объектов / массивов в строку GET-запроса в jQuery есть метод param (который используется ей самой, например, в ajax()):
var myparams = {a:"a",b:"b"};
var str = $.param(myparams); // запишет в str "a=a&b=b"

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

Error starting Sun's native2ascii: sun.tools.native2ascii.Main

При сборке maven`ом чужого проекта вывалилась ошибка:
Error starting Sun's native2ascii: sun.tools.native2ascii.Main
Причина её в том, что я использовал JDK 1.7. С 1.6 все собралось.

DQL: сравнение дат

Задача:

В некотором атрибуте хранится дата. Необходимо осуществить поиск по этому атрибуту,  но с точностью до даты, т.е. время в поиске участвовать не должно.

Решение:

Использовать функцию DATEFLOOR, например:
SELECT * FROM xxx WHERE DATEFLOOR(day,my_date_attr) = DATEFLOOR(day,DATE('12-12-2012'))
Нужно учитывать, что DATEFLOOR осуществляет округление до времени 00:00:00 по GMT, т.е. если текущий часовой пояс GMT+4, то DATEFLOOR(day,DATE('12-12-2012')) вернет не '12-12-2012 00:00:00', а '12-12-2012 04:00:00'. 

четверг, 13 декабря 2012 г.

java.util.Date to XMLGregorianCalendar

Собственно тривиально:
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
XMLGregorianCalendar xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);

Куда в WSO2 ESB подкладывать свои OSGI-бандлы

Методом проб и ошибок я определил, что свои OSGI-бандлы нужно подкладывать в repository/components/lib, потому что из dropins и extensions они почему-то не видятся. Если добавлять через ESB Artifacts -> Add - тоже О_о (хотя надо ещё раз попробовать).

WSO2 ESB: отправка multipart-сообщения MailTransportSender`ом

Чтобы отправляемое сообщение стало multipart, нужно установить свойство "transport.mail.Format" в области видимости "axis2" в значение "Multipart":
<property name="transport.mail.Format" value="Multipart" scope="axis2"/> 
Толку правда от этого не много. Управлять составом частей формируемого multipart-сообщения мы не можем, оно будет состоять ровно из 2ух частей: первой "text/plain" с текстом "Web Service Message Attached", и второй, содержащей собственно наше сообщение. 

Доступ к объекту org.apache.axis2.context.MessageContext из script-mediator

В коде пользовательского Java-медиатора можно получить доступ к axis2-контексту, прикастовав контекст сообщения Axis2MessageContext и вызвав getAxis2MessageContext() (это может понадобится, например, для получения параметров прокси-сервиса). В script-медиаторе, к сожалению, метода getAxis2MessageContext() нет, при попытке сделать mc.getAxis2MessageContext() валится:
com.sun.phobos.script.util.ExtendedScriptException: org.mozilla.javascript.EcmaError: TypeError: Cannot find function ge
tAxis2MessageContext. (<Unknown Source>#2) in <Unknown Source> at line number 2
        at com.sun.phobos.script.javascript.RhinoCompiledScript.eval(RhinoCompiledScript.java:68)
        at javax.script.CompiledScript.eval(CompiledScript.java:92)
Дело в том, что переменная mc в скрипт-медиаторах является объектом класса org.apache.synapse.mediators.bsf.ScriptMessageContext (а не org.apache.synapse.core.axis2.Axis2MessageContext, как в случае с Java-медиатором), в котором действительно нет метода getAxis2MessageContext(). Поэтому, к сожалению, функционал, который можно реализовать с помощью script-медиатора, несколько уже, чем таковой для Java-медиаторов.

UnsupportedClassVersionError


Исключение:
java.lang.UnsupportedClassVersionError: ... :minor version 51.0
говорит о том, что класс может быть запущен только JRE 7.

вторник, 11 декабря 2012 г.

WSO2 ESB: раздельная настройка логирования для разных прокси-сервисов


В WSO2 ESB можно разделить логи, относящиеся к разным прокси-сервисам. Для этого нужно в файл настройки логирования (repository/conf/log4j.properties) добавить следующее:
log4j.category.SERVICE_LOGGER.MyProxy=FATAL, MYPROXYADAPTER
log4j.additivity.MYPROXYADAPTER=false
log4j.appender.MYPROXYADAPTER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MYPROXYADAPTER.File=${carbon.home}/repository/logs/custom/MyProxy.log
log4j.appender.MYPROXYADAPTER.Append=true
log4j.appender.MYPROXYADAPTER.layout=org.apache.log4j.PatternLayout
log4j.appender.MYPROXYADAPTER.layout.ConversionPattern=%d{HH:mm:ss,SSS}%n%m%n
 , где "MyProxy" - имя прокси-сервиса, для которого настраивается логирование. Конфигурация аппендера может быть любой другой, здесь я её привел просто для примера.


WSO2 ESB 4.5.1: разделитель - новая строка или табуляция в Log-медиаторе

Задача:

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

Решение:

Использовать в XML-конфигурации в качестве разделителя числовую ссылку на символ, соответствующую символу перевода строки (line feed) - "&#x000A;" или табуляции - "&#x0009;":
<log level="full" category="INFO" separator="&#x000A;">
<property name="myprop" expression="synapse:get-property('myprop')"/>
</log>

Carriage return и line feed в виде numeric character reference в XML

Символу Carriage return (CR, \r) соответствует числовая ссылка на символ (numeric character reference, не знаю как менее коряво перевести) "&#x000D;", а line feed (LF, \n) - "&#x000A;".

суббота, 8 декабря 2012 г.

WSO2 ESB: UnmappableCharacterException при отправке сообщения sender`ом HttpCoreNIOSender

Проблема:

Есть 2 прокси-сервиса, один - VFS -> JMS, принимающий файлы, обрабатывающий их и сладывающий сформированный SOAP в очередь, и другой - JMS->HTTP, отправляющий это сообщение конечному сервису. В последнем при отправке сообщения sender`ом HttpCoreNIOSender валится исключение:
[2012-12-06 10:20:18,108] ERROR - ClientHandler I/O Error submitting request : Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
        at java.nio.charset.CoderResult.throwException(CoderResult.java:278)
        at org.apache.http.impl.nio.reactor.SessionOutputBufferImpl.writeLine(SessionOutputBufferImpl.java:160)
        at org.apache.http.impl.nio.codecs.AbstractMessageWriter.write(AbstractMessageWriter.java:93)
        at org.apache.synapse.transport.nhttp.LoggingNHttpClientConnection$LoggingNHttpMessageWriter.write(LoggingNHttpC
lientConnection.java:137)
        at org.apache.http.impl.nio.DefaultNHttpClientConnection.submitRequest(DefaultNHttpClientConnection.java:241)
        at org.apache.synapse.transport.nhttp.LoggingNHttpClientConnection.submitRequest(LoggingNHttpClientConnection.ja
va:78)
        at org.apache.synapse.transport.nhttp.ClientHandler.processConnection(ClientHandler.java:256)
        at org.apache.synapse.transport.nhttp.ClientHandler.connected(ClientHandler.java:204)
        at org.apache.http.impl.nio.DefaultClientIOEventDispatch.connected(DefaultClientIOEventDispatch.java:134)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionCreated(BaseIOReactor.java:284)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.processNewChannels(AbstractIOReactor.java:419)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:286)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
        at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:54
2)
        at java.lang.Thread.run(Thread.java:722)

Причина:

Причина оказалась в русских именах файлов. Дело в том, что VFS кладет имя файла и полный путь к нему в транспортные заголовки "FILE_NAME" и "FILE_PATH". Эти заголовки сохраняются при прохождении сообщения через JMS и попадают sender`у. А тот, видимо, не любит не-ASCII-символы в заголовках и валится. В качестве workaround`а можно удалять проблемные заголовки перед отправкой сообщения:
<property action="REMOVE" name="FILE_NAME"
                scope="transport" value=""/>
<property action="REMOVE" name="FILE_PATH"
                scope="transport" value=""/>
, или, если они нужны, скажем, в out sequence, можно перекладывать значения в свойства в области видимости default.

среда, 5 декабря 2012 г.

DFC: получение идентификатора объекта, созданного с помощью CREATE DQL-запроса

При выполнении CREATE DQL-запроса с помощью DfQuery идентификатор объекта возвращается как аттрибут "object_created" единственной строки результирующей коллекции:
IDfCollection col = query.execute(session, DfQuery.DF_QUERY);
col.next();
IDfId newId = col.getId("object_created");

WSO2 ESB 4.5.1: "org.apache.axis2.AxisFault: Transport error: 415 Error: Unsupported Media Type" при отправке сообщения на backend-сервис

Проблема:

При проксировании запроса возникает ошибка:
[2012-12-05 18:21:58,341]  INFO - HTTPSender Unable to sendViaPost to url[http://localhost:8084/mysrv]
org.apache.axis2.AxisFault: Transport error: 415 Error: Unsupported Media Type
        at org.apache.axis2.transport.http.HTTPSender.handleResponse(HTTPSender.java:308)
        at org.apache.axis2.transport.http.HTTPSender.sendViaPost(HTTPSender.java:194)
        at org.apache.axis2.transport.http.HTTPSender.send(HTTPSender.java:75)
        at org.apache.axis2.transport.http.CommonsHTTPTransportSender.writeMessageWithCommons(CommonsHTTPTransportSende
.java:450)
        at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:277)
        at org.apache.axis2.engine.AxisEngine$TransportNonBlockingInvocationWorker.run(AxisEngine.java:627)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:722)
Причина:

Заголовок Content-Type, передаваемый шиной конечному веб-сервису, не поддерживается последним. В моем случае значение заголовка было "application/xml", тогда как сервис ожидал "text/xml" (о чем кстати он и писал в свои логи).

Решение:

В цепочке медиации установить свойство "messageType" в области видимости "axis2":
<property name="messageType" value="text/xml" scope="axis2"/>
Тогда Message Builder сформирует правильное значение HTTP-заголовка. 

WSO2 ESB: Установка заголовка SOAPAction

Установка заголовка SOAPAction для исходящего сообщения выполяется с помощью медиатора "header":
<header action="set" name="Action" value="someSoapAction"/>

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

Synapse: использование пространств имен в Xpath в конфигурации

Чтобы конфигурации Synapse использовать пространства имен в выражениях Xpath, нужно в в том теге, в котором xpath используется (или в любом теге-предшественнике этого тега) объявить нужный namespace, после чего его префикс можно будет использовать в Xpath:
<switch source="count($body/myns:MyNode)" xmlns:myns="http://my.ns"

Commons Codec 1.4: encodeBase64String и multi-line chunking

Версии Commons Codec до 1.5 выполняют multi-line chunking (разбитие результата на строки равной длины) при вызове encodeBase64String(byte[] binaryData): http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64String(byte[]). Т.к. в WSO2 ESB 4.5.1 входит именно версия 1.4, приходится с этим мириться. Если нужно все же получить результат одной строкой, придется сделать replace:
Base64.encodeBase64String(strToEncode).replace(System.lineSeparator(),"")
Или, для 6ой явы:
Base64.encodeBase64String(strToEncode).replace(System.getProperty("line.seperator"),"")

понедельник, 3 декабря 2012 г.

WSO2 ESB 4.5.1: установка имени очереди для JMS-транспорта прокси-сервиса

По-умолчанию, JMS-транспорт прокси-сервиса слушает очередь, имя которой совпадает с именем самого сервиса. Чтобы указать имя очереди явно, нужно задать параметр  сервиса "transport.jms.Destination":
<proxy xmlns="http://ws.apache.org/ns/synapse"
    name="myProxy" transports="jms">
    ...
<parameter name="transport.jms.Destination">myQueue</parameter>
</proxy>

четверг, 29 ноября 2012 г.

WSO2ESB 4.5.1: настройка связки с Apache ActiveMQ 5.7.0

1) Скопировать из дистрибутива Apache ActiveMQ  5.7.0 следующие jar-ники в /repository/components/extensions шины:
  • activemq-core-5.7.0.jar
  • geronimo-j2ee-management_1.1_spec-1.0.1.jar
  • slf4j-api-1.6.6.jar
2) Раскомментировать в axis2.xml настройки receiver`а и sender`а (предполагается, что ActiveMQ стоит на локальной машине и слушает порт по-умолчанию, 61616):
<transportReceiver name="jms" class="org.apache.axis2.transport.jms.JMSListener">
<parameter name="default" locked="false">
<parameter name="java.naming.factory.initial" locked="false">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter>
<parameter name="java.naming.provider.url" locked="false">tcp://localhost:61616</parameter>
<parameter name="transport.jms.ConnectionFactoryJNDIName" locked="false">QueueConnectionFactory</parameter>
<parameter name="transport.jms.ConnectionFactoryType" locked="false">queue</parameter>
</parameter>
</transportReceiver>
...
<transportSender name="jms" class="org.apache.axis2.transport.jms.JMSSender"/>
3) Всё, после перезапуска шины можно использовать JMS. Например, для конечных точек:
<endpoint>
<address encoding="UTF-8" statistics="disable"
trace="disable" uri="jms:/myQueue?transport.jms.ConnectionFactoryJNDIName=QueueConnectionFactory&amp;java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory&amp;java.naming.provider.url=tcp://localhost:61616&amp;transport.jms.DestinationType=queue"/>
</endpoint>


пятница, 23 ноября 2012 г.

Как сделать Pretty-print для JavaScript

Встала тут задача привести JavaScript к более ли менее читаемому виду (сам его пару лет назад минифицировал, а исходный, неужатый вариант, благополучно посеял). Есть множество онлайн сервисов, рабочих и не очень, но самый, по-моему, удобный вариант - воспользоваться встроенными возможностями Google Chrome. Здесь все просто: выбираем нужный скрипт, и вуаля - он pretty-printed (если нет - нужно нажать кнопку "Pretty print" внизу):


Жаль имена переменных говорящими уже не сделать =)

Commons VFS: получения доступа к оригинальному объекту File для LocalFile

Задача:

С помощью Commons VFS в приложении получается объект FileObject. Точно известно, что он представляет собой локальный файл (т.е. представлен имплементацией org.apache.commons.vfs2.provider.local.LocalFile). Нужно получить обычный джавовский объект File для этого файла.

Решение:

Если посмотреть в документацию по VFS, то можно увидеть, что класс LocalFile имеет метод getLocalFile(), возвращающий нужный нам File. Незадача заключается в том, что этот метод - защищенный (вообще я заметил, что в большинстве Open Source проектов самые нужные методы постоянно оказываются protected или вообще private). Наследоваться от LocalFile и делать метод публичным смысла не имеет - ведь файл приходит к нам именно в виде LocalFile. Однако, в Java защищенные члены видны не только наследникам, но и всем классам того же пакета, т.е. нам ничего не мешает создать некий Helper-класс, разместить его в пакете org.apache.commons.vfs2.provider.local.LocalFile, и этим классом сделать нужный нам метод доступным:

package org.apache.commons.vfs2.provider.local;
import java.io.File;
public class LocalFileHelper
{
    public static File getFile(LocalFile file)
    {
      return file.getLocalFile();
    }
}
UP ну а вообще можно сделать гораздо проще =)  :
public class LocalFileHelper
{
    public static File getFile(LocalFile file)
    {
    new File(file.getName().getURI().replaceFirst("^file:///", ""));
    }
}
и тут уже все равно, в каком пакете наш хелпер.

WSO2 ESB: java.io.IOException: line too long при развертывании Carbon Application

Проблема:

При старте WSO2 ESB 4.5.1 после развертывания Carbon Application валится исключение:
[2012-11-23 18:31:16,255] ERROR {org.wso2.carbon.server.extensions.DropinsBundle
Deployer} -  Error occured while deploying bundles in the dropins directory
java.io.IOException: line too long
        at java.util.jar.Attributes.read(Attributes.java:379)
        at java.util.jar.Manifest.read(Manifest.java:199)
        at java.util.jar.Manifest.<init>(Manifest.java:69)
        at java.util.jar.JarFile.getManifestFromReference(JarFile.java:179)
        at java.util.jar.JarFile.getManifest(JarFile.java:165)
        at org.wso2.carbon.server.extensions.DropinsBundleDeployer.getNewBundleI
nfoLines(DropinsBundleDeployer.java:95)
        at org.wso2.carbon.server.extensions.DropinsBundleDeployer.perform(Dropi
nsBundleDeployer.java:61)
        at org.wso2.carbon.server.Main.invokeExtensions(Main.java:128)
        at org.wso2.carbon.server.Main.main(Main.java:74)
Причина:

В одном из jar-ников директории  repository/components/dropins/ слишком длинная строка в файле манифеста META-INF/MANIFEST.MF, скорее всего, это строка Export-Package в случае, если в jar-нике много экспортируемых пакетов. Нужно разбить эту строку на несколько с помощью перевода строки и пробела (если в начале строки манифеста стоит пробел, она считается продолжением предыдущей строки), например:
Export-Package: my.package1, my.package2,my.package3
 my.package4, my.package5, my.package6, my.package7
 my.package8

WSO2ESB: доступ к параметру прокси-сервиса из пользовательского медиатора

Чтобы прочитать какой-либо параметр прокси-сервиса из кода пользовательского медиатора, нужно:
public boolean mediate(MessageContext context) {
org.apache.axis2.context.MessageContext axis2MsgContext;
axis2MsgContext = ((Axis2MessageContext)context).getAxis2MessageContext();
axis2MsgContext.getAxisService().getParameter("paramName");
...
}

JAXB: trim текстового содержимого тегов при unmarshal

Для выполнения неких операций над текстовым содержимым тегов в XML-документе при его анмаршаллинге, предназначены XML-адаптеры, т.е. классы, наследуемые от javax.xml.bind.annotation.adapters.XmlAdapter. Для выполнения trim такой адаптер выглядит так (он выполняет trim как при маршаллинге, так и при анмаршаллинге):
public class StringTrimAdapter extends XmlAdapter<String, String> {
    @Override
    public String unmarshal(String v) throws Exception {
        if (v == null)
            return null;
        return v.trim();
    }
    @Override
    public String marshal(String v) throws Exception {
        if (v == null)
            return null;
        return v.trim();
    }
}
 Подключать адаптер можно как на уровне элемента:
@XmlElement(required=true)
@XmlJavaTypeAdapter(StringTrimAdapter.class)
String name;
, так и на уровне пакета, если требуется навесить адаптер на все классы пакета. Последнее выполняется в package-info.java этого пакета:
@XmlJavaTypeAdapter(value=StringTrimAdapter.class,type=String.class)
package my.package;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import my.package.adapters.StringTrimAdapter;
(здесь адаптер вышается на классы пакета my.package, а сам адаптер расположен в пакете my.package.adapters)


WSO2 ESB: логирование из медиатора

org.apache.synapse.mediators.AbstractMediator, от которого наследуется любой пользовательский медиатор, имеет 2 элемента для логирования: log и trace. С последним все не так прозрачно, а вот настроить первый просто: нужно добавить в /repository/conf/log4j.properties:
log4j.logger.package.MediatorClass=DEBUG
, где package - пакет, в котором расположен медиатор, MediatorClass - собственно имя класса медиатора. Вместо DEBUG можно установить другой уровень. Ошибки будут валиться в консоль и в wso2carbon.log, а те, уровень которых выше или равен WARN - ещё и в wso2-esb-errors.log.

junit-тексты в maven-проекте NetBeans: как получать доступ к необходимым для теста файлам

С ant-проектами все было просто, файлы можно было класть, к примеру, в /test/files, и читать в текст-кейсах так:
File f1 = new File("test/files/text.xml");
C maven-ом все сложнее, и делается так: во-первых, в /src/test/ проекта нужно создать папку "resources", и сложить в неё все необходимые тестам файлы. Теперь получить доступ к ним из тест-кейса можно так:
String fileName = URLDecoder.decode(this.getClass().getClassLoader().getResource("test.xml").replace("+", "%2B").replace("%20","+"),"UTF-8");
File f1 = new File(fileName);
Здесь  replace`ы требуются для приведения RFC3986-кодированного URL к формату HTML 4 (http://barbitoff.blogspot.ru/2012/04/javaneturlencoderencode.htmlhttp://barbitoff.blogspot.ru/2011/11/javascript-url-encode-decode.html). Длинновато, но работает. Можно вынести в какой-нибудь статический метод и использовать во всех тест-кейсах.

четверг, 22 ноября 2012 г.

WSO2 ESB: доступ к транспортному заголовку из медиатора

Доступ к транспортным заголовкам из медиатора осуществляется немного нетривиально:
public boolean mediate(MessageContext context)
{
org.apache.axis2.context.MessageContext axis2MsgContext;
axis2MsgContext = ((Axis2MessageContext)context).getAxis2MessageContext();
Map transportHeaders = (Map)axis2MsgContext.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
System.out.println("FILE_NAME header is:\n\n"+(String)transportHeaders.get("FILE_NAME")+"\n\n");
}
В итоге выведенное в консоли значение будет совпадать с получаемым с помощью лог-медиатора:
<log level="custom">
<property name="FILE_NAME" expression="synapse:get-property('transport','FILE_NAME')"/>
</log>

среда, 21 ноября 2012 г.

WSO2 ESB: Получение имени файла, полученного по траспорту VFS, в цепочке медиации

Получается имя файла XPath-выражением:
synapse:get-property('transport','FILE_NAME')
Например, логируется это свойство в цепочке медиации так:
<log level="custom">
<property name="vfsfilename" expression="synapse:get-property('transport','FILE_NAME')"/>
</log>
Как получить доступ к транспортному заголовку ('FILE_NAME' или любому другому) из пользовательского медиатора, я писал тут: http://barbitoff.blogspot.ru/2012/11/wso2-esb.html

Немного offtop: отсутствие в SWIFT-сообщении поля 50K

Если в SWIFT-сообщении поля 50K нет, значит плательщик - физическое лицо, информация о нем размещается в поле 50F.

Maven-проект в Netbeans: как загрузить все зависимости в одну папку

Задача:

Есть Maven-проект Netbeans 7.2.1, необходимо, чтобы при сборке jar-ники всех зависимостей загружались в какую-либо папку. Действие "Build with Dependencies" почему-то не производит ожидаемого эффекта.

Решение:
  1. Свойства проекта -> Actions -> Выбираем "Build with Dependencies"
  2. В "Execute Goals" добавляем "dependency:copy-dependencies" через пробел после "install":

  3. Теперь при выполнении "Build with Dependencies" зависимости будут копироваться в /target/dependency проекта. Вообще, очень похоже, что именно так и должно было происходить при нажатии "Build with Dependencies", но разработчики Netbeans куда-то потеряли нужный гол из конфигурации действия.

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

Java: p6spy или JDBC-logging anywhere

Иногда возникает необходимость трэйсить SQL-запросы к БД в Java-приложении. Если приложение свое (или чужое, но есть доступ к исходникам и возможность их пересобрать) - проблема решается просто:
DriverManager.setLogWriter(writer);
Connection con = DriverManager.getConnection(...);
...
или
DataSource.setLogWriter(writer);
Если доступа к исходникам нет - тут уже все становится сложнее. Предположим, что у нас есть хотя бы доступ к некоторому properties-файлу, в котором задаются параметры JDBC-соединения изучаемого приложения: драйвер, URL и т.п., например:
# The database driver
database.driver = transbase.jdbc.Driver
# The database server or connection string
database.connectionString = jdbc:transbase://localhost:2024/mydb
# Database Login and Password
database.user = iamadmin
...
В таком случае возникает естественная идея - нельзя ли вместо настоящего драйвера подложить какой-нибудь свой, который будет трэйсить запросы и затем отправлять их уже настоящему драйверу БД. Сделать это действительно можно, да и прокси-драйвер такой уже написан. Более того, он является бесплатным (распространяется по Apache Software License). Называется этот драйвер p6spy, доступен для скачки здесь: http://sourceforge.net/projects/p6spy/.
Вот пример подключения p6spy к веб-приложению, развернутому на Tomcat 6:
  1. Качаем с sourceforge файл p6spy-install.jar, распаковываем 
  2. Кладем p6spy.jar в /webapps/<appname>/WEB-INF/lib 
  3. Кладем spy.properties в /webapps/<appname>/WEB-INF/classes
  4. Меняем название драйвера в properties-файле приложения на "com.p6spy.engine.spy.P6SpyDriver" (все остальные параметры подключения не трогаем)
  5. Редактируем spy.properties:
    logfile = D:\spy.log
    excludecategories=
    realdriver=transbase.jdbc.Driver
    Здесь мы задали файл, куда будут писаться логи (я на всякий случай его заранее создал), убрали все исключения из логирования, чтобы в логи писалось абсолютно все, что можно, а также задали имя настоящего драйвера.
  6. Готово, перезагружаем веб-приложение (или томкат целиком), читаем логи =)

пятница, 16 ноября 2012 г.

WSO2 BAM 2.0.x: бинд порта для Thrift SSL на внешний сетевой интерфейс

WSO2 BAM 4.0.x биндит порт 7611 (по-умолчанию для Thrift) как на 127.0.0.1, так и на внешний сетевой интерфейс, а вот 7711 (по-умолчанию для Thrift SSL) - почему-то только на внешний. Поэтому при настройке отсылки событий на BAM использовать localhost нельзя, только внешний IP.

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

Как узнать пароль уже имеющегося в системе ODBC Data Source

Задача

В системе есть некий ODBC DataSource, но при его редактировании пароль закрыт звездочками (или точками, не суть). Нужно узнать его.

Решение

В зависимости от того, системный DSN или пользовательский, лезем в :
HKEY_LOCAL_MACHINE\Software\Odbc\Odbc.ini\
или
HKEY_CURRENT_USER\Software\Odbc\Odbc.ini\
, ищем там нужный датасорс по имени и смотрим пароль там ;)

Ruby: bundle за прокси с авторизацией

Для работы gem за прокси (с авторизацией или без неё) предусмотрен специальный параметр --http-proxy:
gem ... --http-proxy http://user:password@host:port
Для bundle такого параметра нет, но управлять использованием прокси можно через переменную окружения "http_proxy":
export http_proxy=http://user:password@host:port
bundle install ... 

среда, 7 ноября 2012 г.

away3d 4: 3D-модели, экспортируемые из Prefab3D, и утечка памяти

Полчаса размышлений, и источник утечки памяти, возникающей при пересоздании 3D-объектов, экспортированных из Prefab3D (версия - 2.124), найден. В классе ASBase, являющимся базовым для всех экспортируемых 3D-моделей, отсутствует корректное переопределение метода dispose(), очищающее занимаемые ресурсы. Переопределив метод так:

public override function dispose():void
{
super.dispose();
var i:uint;
for(i=0;i<_meshes.length;i++)
_meshes[i].dispose();
_meshes=null;
for(i=0;i<_containers.length;i++)
_containers[i].dispose();
_containers=null;  
for(i=0;i<_materials.length;i++)
_materials[i].dispose();
_materials=null;
}  
мы избавляемся от утечки памяти.

пятница, 2 ноября 2012 г.

PostgreSQL: прочитать строку из hex-представления

Задача:

В колонке таблицы хранится строка, представленная в виде шестнадцатеричных кодов своих символов в UTF8-кодировке (тип колонки - text). Нужно извлечь из неё строковые значения.

Решение:
SELECT convert_from(decode("a",'hex'),'UTF8') FROM ...
где "а" - имя колонки с hex-кодированными строками.

away3d 4.0: view.camera.project() и инициализация сцены

Столкнулся с непонятной для меня особенностью away3d 4.0: если использовать метод  view.camera.project() для проецирования 3D-точки в 2D-плоскость экрана в конструкторе основного спрайта флешки, или же в обработчике его события Event.ADDED_TO_STAGE, проецирование выполняется неверно (по крайней мере, оно не совпадает с последующими попытками спроецировать ту же точку). Тоже поведение наблюдается и при использовании метода в обработчике Event.ENTER_FRAME при его первых 3-4 срабатываниях. 
Описанное выше поведение я замечаю при использовании линзы OrthographicLens, а ошибка проецирования заключается в сжатии проекции по оси Ox.
Как оказалось, на линзе возникает событие LensEvent.MATRIX_CHANGED, до которого проецирование неверное, а после которого становится верным. Поэтому, если после инициализации флешки есть необходимость спроецировать какую-либо 3d-точку с использованием камеры, нужно дождаться сначала срабатывания события LensEvent.MATRIX_CHANGED.

пятница, 26 октября 2012 г.

Порядок развертывания веб-приложений на Tomcat 6 при его запуске

К сожалению, как сказано в вики Томката, порядок развертывания веб-приложений не определен, и задать его каким бы то ни было образом нельзя. Единственный способ обойти это - задать два различных сервиса в конфигурации, они при этом будут иметь различные порты, но веб-приложения следующего всегда будут разворачиваться позже веб-приложений предыдущего.

среда, 24 октября 2012 г.

away3d: скриншоты, или как отрисовать View3D на Bitmap

В away3d 4.0 делать скриншоты содержимого флешки простым:
myBitmap.draw(flashSprite);
не получится (здесь myBitmap - это объект BitmapData, в который запишется скриншот, а flashSprite - корневой спрайт флешки). Выходит это по той причине, что Stage3D не принадлежит списку отрисовки, и в битмапе мы получим все, кроме 3D-контента.
Однако, есть способ получить в битмап содержимое 3D-вида (объекта View3D):
_view.renderer.swapBackBuffer = false;
_view.render();
_view.stage3DProxy.context3D.drawToBitmapData(myBitmap);
_view.renderer.swapBackBuffer = true;
Хитро, но работает, спасибо форуму away3d: http://away3d.com/forum/viewthread/1960/.

вторник, 23 октября 2012 г.

Ошибка "org.xml.sax.SAXParseException: The processing instruction target matching [xX][mM][lL] is not allowed" при парсинге XML

Проблема:

При разборе XML-документа валится исключение:
org.xml.sax.SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed.
at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
Возможная причина:

Во входящем документе несколько раз присутствует XML Declaration (например, <?xml version="1.0" encoding="UTF-8" ?>).


суббота, 20 октября 2012 г.

"Invalid operation on closed cursor" при просмотре данных в таблице БД Transbase из Netbeans

Попытался посмотреть БД Transbase, используя из JDBC-драйвер и Netbeans. Подключился без проблем, список таблиц получил, а вот SELECT`ы не выполняются, валится ошибка:
Код ошибки 30004, положение SQL : TB Java Client error <30004>: Invalid operation on closed cursor
Строка 1, столбец 1
То ли дело в кривом драйвере, то ли в Netbeans`е. Пришлось искать другой способ полазить по БД, и я наткнулся на вполне годную программку JDBC Navigator: http://thomasokken.com/jdbcnav/. Она показывает данные таблиц без проблем, используя тот же драйвер.

среда, 17 октября 2012 г.

JAX-WS-клиент веб-сервиса: ошибка "com.sun.xml.internal.ws.model.RuntimeModelerException: runtime modeler error: Wrapper class <xxx> is not found. Have you run APT to generate them?"

Проблема:

Веб-приложение, использующее JAX-WS-клиент к веб-сервису, отлично работало на машине разработчика, а при развертывании на Tomcat на промышленном сервере стало валиться с ошибкой:
com.sun.xml.internal.ws.model.RuntimeModelerException: runtime modeler error: Wrapper class <xxx> is not found. Have you run APT to generate them?
Причина:

Вероятнее всего, старый JDK.

Решение:

Обновить JDK. Не знаю, до какой версии точно, но обновление до последнего JDK (1.6.0_26 в  репозитории Debian Lenny) в моем случае решило проблему. 

apt-get: обновление определенного пакета

Чтобы обновить один лишь пакет (а не все установленные пакеты, как это делает apt-get upgrade), нужно всего лишь воспользоваться командой install, которая в случае, если пакет уже установлен, обновляет его до последней доступной версии.

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

Orbeon: "java.io.IOException: PDF header signature not found." при печати формы

Проблема:

При попытке распечатать форму Orbeon Forms (имеющую собственный шаблон pdf-ки) валится исключение:
java.io.IOException: PDF header signature not found.
at com.lowagie.text.pdf.PRTokeniser.checkPdfHeader(Unknown Source)
at com.lowagie.text.pdf.PdfReader.readPdf(Unknown Source)
at com.lowagie.text.pdf.PdfReader.<init>(Unknown Source)
at com.lowagie.text.pdf.PdfReader.<init>(Unknown Source)
...
Причина:

Вероятнее всего, pdf-ки просто нет там, куда ссылается соотв. элемент формы (instance('fr-form-attachments')/pdf).

среда, 10 октября 2012 г.

YUI2: динамическое изменение высоты виджета YAHOO.widget.Panel

Изменение высоты виджета YAHOO.widget.Panel после его создания выполняется вызовом:
panel.cfg.setProperty("height","<xxx>px")
, где panel - сам виджет, созданный с помощью new YAHOO.widget.Panel().

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

Spring Security: сброс сессии определенного пользователя

Чтобы сбросить сессию определенного пользователя, во-первых, нужно получить доступ к реестру сессий. Как это делается, я уже писал в прошлом посте. В конфигурации Spring Security отличие состоит лишь в том, что нам понадобится не только пометить определенную сессию как более не действительную, а ещё и разлогинить пользователя с недействительной сессией (само это не сделается). Для последнего нужен специальный фильтр, тот же, что используется для контроля множественного входа:
<http ...>    
<custom-filter before="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
...
</http>
<!-- Создаем фильтр,ссылающий на реестр сессий. Задаем страницу,
  -- на которую будет выкидывать разлогиненого пользователя
  -->
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/login.jsp?invalidSessionError=1" />
 </beans:bean>
Всё, теперь пользователь с недействительной сессией будет выкидываться на  "/login.jsp?invalidSessionError=1", можно разлогинивать:
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
SessionRegistry sReg = (SessionRegistry) ac.getBean("sessionRegistry");
for(Object principal: sReg.getAllPrincipals())
{
UserDetails authentication = (UserDetails)principal;
if(authentication.getUsername().equals(username))
{
List<SessionInformation> userSessions = sReg.getAllSessions(principal, false);// получаем все сессии пользователя, кроме истекших
for(SessionInformation sessInfo: userSessions)
sessInfo.expireNow();
}
}
Этот код разлогинит пользователя, имя которого задано переменной username. Написанное выше верно для случая, когда принципал представлен объектом UserDetails. 

Spring Security 3.1: получение списка залогиненных пользователей

Задача:

Получить список залогиненных пользователей (например, чтобы разлогинить некоторых из них).

Решение:

Для решения этой задачи можно воспользоваться реестром пользовательских сессий, который используется Spring Security, в частности, для контроля множественного входа под одним логином. Чтобы реестр заполнялся и к нему можно было получить доступ из собственного кода, нужно (если, конечно, у Вас уже не используется контроль множественного входа):
  1. В web.xml добавить слушателя:
    <listener>
      <listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
      </listener-class>
    </listener>
  2. В конфигурацию Spring Security добавить:


    <!-- Задаем точку входа и страницу запрета доступа, обязательно
      -- отключаем auto-config, чтобы определить свой фильтр для входа
      -- по логину/паролю
      -->
    <http auto-config="false" access-denied-page="/login.jsp?accessDeniedError=1"
              entry-point-ref="authenticationProcessingFilterEntryPoint">

            <custom-filter position="FORM_LOGIN_FILTER" ref="usernamePasswordFilter" />
         
    <!-- Далее идут правила доступа -->
            <intercept-url ... />
    ...  
    <!-- Указываем ссылку на бин для session-managment`а -->
            <session-management session-authentication-strategy-ref="sas"/>
    <!-- Конфигурация выхода обыкновенная -->
            <logout delete-cookies="JSESSIONID" logout-url="/j_spring_security_logout"/>
    </http>

    <!-- Конфигурируем точку входа в приложение -->
    <beans:bean id="authenticationProcessingFilterEntryPoint"
    class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <beans:property name="loginFormUrl" value="/login.jsp" />
    <beans:property name="forceHttps" value="false" />
    </beans:bean>

    <!-- Конфигурируем реестр сессий -->
    <beans:bean id="sessionRegistry"
    class="org.springframework.security.core.session.SessionRegistryImpl" />

    <!-- Конфигурируем бин стратегии контроля сессий. Не ограничиваем
      -- множественность входа (нам это сейчас не нужно, по этой же причине
      -- мы не использовали фильтр ConcurrentSessionFilter)
      -->
    <beans:bean id="sas" class=
    "org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
    <beans:constructor-arg ref="sessionRegistry" />
    <beans:property name="maximumSessions" value="-1" />
    </beans:bean>

    <!-- Конфигурируем фильтр входа по логину / паролю, задавая ссылку на стратегию,
      -- а также ссылки на бины обработчиков успешного / неуспешного входа
      -->
    <beans:bean id="usernamePasswordFilter" class=
                "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="sessionAuthenticationStrategy" ref="sas" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="authenticationFailureHandler" ref="loginFailureHandler"/>
    <beans:property name="authenticationSuccessHandler" ref="loginSuccessHandler"/>
    </beans:bean>

    <!-- Менеджер аутентификации. Тут все стандартно, задается один какой-то провайдер
      -- и для стандартного провайдера задается какой-то UserService
      -->
    <authentication-manager alias="authenticationManager">
    <authentication-provider ref="..."/>
    <authentication-provider user-service-ref='...'>
    <password-encoder hash="md5" />
    </authentication-provider>
    </authentication-manager>

    <!-- Обработчики успешного / неуспешного логина. Стандартные -->
    <beans:bean id="loginSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler" >
    <beans:property name="defaultTargetUrl" value="/index.jsp"/>
    </beans:bean>

    <beans:bean id="loginFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" >
    <beans:property name="defaultFailureUrl" value="/login.jsp?loginError=1" />
    </beans:bean>

  3. Теперь получить реестр и список активных принципалов в коде можно так:
    import org.springframework.security.core.session.SessionRegistry;
    import org.springframework.web.context.support.WebApplicationContextUtils;

    ...

    ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    SessionRegistry sReg = (SessionRegistry) ac.getBean("sessionRegistry");
    for(Object principal: sReg.getAllPrincipals())
         System.out.println("Principal: "+principal.toString());

PS Надо, пожалуй, озаботиться подсветкой синтаксиса в блоге =)

Spring Security 3.1: AuthenticationProcessingFilterEntryPoint

В Spring Security 3.1 нет AuthenticationProcessingFilterEntryPoint, в отличие от версий 3.0.х, вместо него есть аналогичный класс org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint. Инициализируется через spring security namespace он совершенно также:

<beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
  <beans:property name="loginFormUrl" value="/login.jsp" />
  <beans:property name="forceHttps" value="false" />
</beans:bean>

пятница, 5 октября 2012 г.

php: установка заголовков запроса при загрузке данных по http с помощью функции file_get_contents()

Чтобы установить какой-нибудь заголовок http-запроса при загрузке данных по http функцией file_get_contents(), нужно использовать третий параметр этой функции - $context. Например, чтобы установить User-Agent как у 15ого Firefox`а, нужно:
$opts = array('http'=>array('header' => "User-Agent:Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1\r\n"));
$context = stream_context_create($opts);
$html = file_get_contents($link,false,$context);
А то некоторые сайты, например, без этого плюются 500ой ошибкой и не дают себя по-человечески сграбить =)
Warning: file_get_contents(http://***.ru/***): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error

среда, 3 октября 2012 г.

cmd: смонтировать папку как локальный диск

Чтобы смонтировать, например, папку D:\a\b\c как диск Q:, нужно:
SUBST Q: D:\a\b\c

away3d: двусторонний материал и освещение

Установив у материала свойство bothSides=true, можно добиться, чтобы обе стороны граней, на которые материал наложен, были видимыми (в противном случае одна будет видимой, другая - прозрачной). Однако, если к материалу применено освещение (установлено свойство lightPicker), вторая сторона будет некорректно вести себя по отношению к свету. Т.е., например, если освещение падает только со стороны наблюдателя, одна сторона грани будет корректно освещена, в вот вторая будет казаться неосвещенной.
Дело в том, что освещение грани рассчитывается исходя из угла между нормалью к ней и направлением света. При этом нормаль у грани одна на обе стороны, и для одной стороны она оказывается "выходящей" навстречу наблюдателю, и, значит, освещению, а для второй - "входящей", сонаправленной с направлением света.
Варианта выхода из данной ситуации 2: либо научить away3d самостоятельно инвертировать нормаль при расчете освещенности обратной стороны, либо дублировать все грани фигуры, разворачивая их нормали в обратную сторону. Последнее делается просто, воспользовавшись away3d.tools.helpers.MeshHelper`ом:
var mesh:Mesh = new Mesh(geometry, material);
var duplicateMesh =  new Mesh( geometry.clone(), material);
MeshHelper.invertFaces(duplicateMesh);
Первый вариант сложнее, я пока не копал в эту сторону, хотя с целью уменьшения числа граней и экономии ОП, пожалуй, стоит попробовать. 

вторник, 2 октября 2012 г.

Mint с Mate: как убрать "Мой компьютер" и "Домашняя папка пользователя" с рабочего стола

Чтобы убрать значки "Мой компьютер" и "Домашняя папка пользователя" с рабочего стола, нужно:
  1. Открыть меню программ, зайти в "Параметры" -> "Внешний вид рабочего стола"
  2. Снять соответствующие галочки

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

Xftp 4: кодировка имен файлов

Чтобы вместо русских букв в именах файлов при подключении к Linux-хостам не появлялись кракозябры, нужно:
File -> Properties -> Options -> Поставить галку "Use UTF-8 encoding"

пятница, 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-сцену поверх как-нибудь заливкой.