logo

12 янв. 2013 г.

BIP 11g: кириллица в названиях отчетов + Internet Explorer

Всем привет!
Сегодня расскажу про решение проблемы с кириллическими названиями отчетов в BI Publisher 11g.
Суть в том, что MS Internet Explorer некорректно отображает имена отчетов, чьи названия в кириллице.



Проблема возникает вследствие потери символа "%" где-то в недрах java-классов BIP для кодированной в UTF8 строки с названием файла результата отчета.

А значит задача нашего workaround в том, чтобы привести подобные "битые" строки к нормальному виду:


С помощью HTTP-сниффера видно, что испорчено значение content-disposition у HTTP-ответа.

Сам workaround заключается в следующем:
1) Создание нового фильтра сервлетов, который будет подменять логику метода setHeader для HttpServletResponse.

2) Сопоставление этого фильтра с xdo-сервлетами (отвечающие за вывод пользователю результатов отчетов).

Привожу java код для класса фильтра:
package oracle.xdo.servlet;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class XxCyrillicFilter implements Filter {
    protected FilterConfig config;
    
    public void init(FilterConfig config) throws ServletException {
        this.config = config;
    }
    
    public void destroy() {
    }
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws ServletException, IOException {
        ServletResponse newResponse = response;

        if (request instanceof HttpServletRequest) {
         String uAgent = ((HttpServletRequest)request).getHeader("User-Agent");
         boolean isMSIE = ( uAgent != null && uAgent.indexOf( "MSIE" ) != -1 );
         if (isMSIE)
          newResponse = new XxResponseWrapper((HttpServletResponse) response);
        }        

        chain.doFilter(request, newResponse);
    } 
}

class XxResponseWrapper extends HttpServletResponseWrapper
{
    
    private String cyr = new String("\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u044B\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044C\u044D\u044E\u044F");
    private String [] lat = {"a","b","v","g","d","e","yo","g","z","i","y","i",
        "k","l","m","n","o","p","r","s","t","u",
    "f","h","tz","ch","sh","sh","'","e","yu","ya"};
    
    public String translitIt(String str)
    {
        str = str.toLowerCase();
        
        StringBuffer newStr = new StringBuffer("");
        char [] chs = str.toCharArray();
        
        for (int i = 0; i < chs.length; i++)
        {
            int k = cyr.indexOf(chs[i]);
            
            if (k != -1) newStr.append(lat[k]);
            else newStr.append(chs[i]);
        }
        
        return newStr.toString();
    }
    
    public XxResponseWrapper(HttpServletResponse response)
    {
        super(response);
    }
    
    public void setHeader(String name, String value)
    {
        if (!"content-disposition".equalsIgnoreCase(name))
         super.setHeader(name, value);
        
        else
        {
            String changedCD = value;
                        
            if (value != null)
            {
             int endIndexFirstPart = value.indexOf("filename=\"") + 10;
             int endIndexUnderscore = value.indexOf("_");
          int endIndexDot = value.indexOf(".");
          
          int endIndex = endIndexUnderscore == -1 ? endIndexDot : endIndexUnderscore;  
          
          String badString = value.substring(endIndexFirstPart, endIndex);
          
          StringBuffer sbuf = new StringBuffer();
                for(int i = 0; i < badString.length(); i++)
                {
                    char ch = badString.charAt(i);
                    
                    if(i % 2 == 0)
                        sbuf.append("%");
                    
                    sbuf.append(ch);
                }                
                
                changedCD = value.substring(0, endIndexFirstPart) + sbuf.toString() + value.substring(endIndex);
             
                if (changedCD.length() > 150)
                 try {changedCD = this.translitIt(URLDecoder.decode(changedCD, "UTF8"));}
                 catch (UnsupportedEncodingException e) {}
            }
            
            super.setHeader("content-disposition", changedCD);
        }
    }
}   

Также выкладываю скомпилированные классы.

Из кода видно, что в переопределенном методе setHeader анализируется:
- является ли header нужным нам. т.е. "content-disposition";
- вызывается ли отчет из браузера IE.
Если все верно, то вычленяется "битая" строка, где перед каждой парой символов вставляется символ процента.
Затем полученная скорректированная строка заменяет битую часть заголовка "content-disposition".
И наконец проверяется длина полученной строки, если она велика ( > 150 ), то пытаемся перевести кириллические символы в транслит (тем самым избегаем проблемы открытия в MS Excel файло с ОЧЕНЬ длинным названием).


Кажется, самая сложная часть закончена...
Но нет!
Теперь нам следует указать веб-приложению xmlpserver использовать созданный фильтр сервлетов, а также сопоставить его с определенными сервлетами.

Тут важно знать, что правка файла web.xml (дескриптор веб-приложений) в stage-каталогах ни к чему не приведет!

Поэтому нам потребуется:
1) В каталоге с ear-архивами основных веб-приложений (..\Middleware\Oracle_BI1\bifoundation\jee )
создать бекап файла xmlpserver.ear, создать ПАПКУ с именем xmlpserver.ear, в которую разархивировать все содержимое исходного ear-архива.


2) Теперь, уже внутри каталога xmlpserver.ear найти архив xmlpserver.war, и в нем-то скорректировать файл
..\WEB-INF\web.xml


3) Добавить в web.xml нужно следующий код:
<filter>
  <filter-name>XxCyrillicFilter</filter-name>
  <filter-class>oracle.xdo.servlet.XxCyrillicFilter</filter-class>
</filter>
 
<filter-mapping>
  <filter-name>XxCyrillicFilter</filter-name>
  <url-pattern>/servlet/xdo</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>XxCyrillicFilter</filter-name>
  <url-pattern>*.xdo</url-pattern>
</filter-mapping>

4) Также следует создать в папке WEB-INF каталог classes c java-классами данного решения (включая полный путь классов - WEB-INF/classes/oracle/xdo/servlet/...)

5) И наконец, следует обновить развернутое веб-приложение bipublisher через консоль Weblogic






Вот и все, теперь, после перезагрузки приложения bipublisher, отчеты в IE будут формироваться следующим образом:

2 комментария:

  1. Сергей,
    Мощно, снимаю шляпу! А ответствующую багу на support.oracle.com завели ?

    ОтветитьУдалить
  2. Роман, спасибо!
    Есть у меня глупая привычка - находить workaround'ы вместо "плодотворной" работы с техподдержкой Oracle.
    Умом понимаю, что заведи я SR - в итоге любимый продукт будет улучшен, но ничего с собой поделать не могу ;)

    ОтветитьУдалить