barbitoff programmer`s blog

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

пятница, 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"