среда, апреля 18, 2012

TextView, ellipsize, maxLines и странности

Есть у меня такой TextView:

    [TextView
        android:id="@+id/news_item_text"
        style="@style/text_plain_embossed"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:ellipsize="end"
        android:maxLines="4"
        android:text="Здесь текст новости, который может быть очень очень очень длинным и не помещаться на одну строку. Шла Маша по шоссе и сосала сушку. Чем дальше в лес, тем толще партизаны. Странный аттрактор не имеет выхода."/]

Всё, что я от него хочу, это:

  • чтобы длинный текст отображался бы в 4 строки (android:maxLines="4" )
  • чтобы в конце текста, если он не помещается целиком, добавлялось бы многоточие ( android:ellipsize="end")
И вот тут начинается самое интересное. В разных версиях (я тут вначале опечатался и набрал слово "весрия", но иногда хочется именно так и оставить) Андроида результат отображения этого представления получается весьма разный. Изображения сняты из эмулятора, но на устройствах они получаются такими же (проверялось на HTC Desire (2.3), Samsung Galaxy Tab 7.7 (3.2) и на Samsung Galaxy S II (4.0)).

Android 2.2

Android 3.2


Android 4.0

Видно, что:
Android 2.2 - количество строк - плохо, многоточие - хорошо.
Android 3.2 - количество строк - хорошо, многоточие - плохо
Android 4.0 - количество строк - хорошо, многоточие - хорошо.

При этом, в если убрать android:ellipsize="end", то в 2.2 получается вот так:
Android 2.2, без ellipsize

И как с этим бороться?!



пятница, апреля 13, 2012

DIP - Debugging Inversion Principle


Не надо находить баги в приложении.
Они сами найдут тебя.

пятница, апреля 06, 2012

Отформатировать копейки в рубли с разделителями групп и с точкой

Понадобилось отформатировать для вывода значения сумм в копейках.
При этом нужны разделители групп, а в качестве разделителя дробной части должна использоваться точка.


private static DecimalFormat currencyFormat;
static {
     currencyFormat = new DecimalFormat();
     DecimalFormatSymbols dfs = currencyFormat.getDecimalFormatSymbols(); 
     dfs.setDecimalSeparator('.'); 
     currencyFormat.setDecimalFormatSymbols(dfs);
     currencyFormat.setMinimumFractionDigits(2); 
     currencyFormat.setMaximumFractionDigits(2); 
     currencyFormat.setGroupingSize(3);
     currencyFormat.setGroupingUsed(true);
}

int amount = ...
double roubles = (double) amount / 100;
return currencyFormat.format( roubles );

среда, марта 21, 2012

Как программно добавить строки с колонками одинаковой ширины в TableLayout

Если коротко - для каждой колонки (т. е. для содержимого TableRow) нужно задать
layout_width="0dp"
layout_weight="1"

Вот пример кода
// Важно импортировать именно этот класс LayoutParams - иначе
// строки просто не покажутся
import android.widget.TableRow.LayoutParams;

import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;



@ViewById(R.id.datatable)
TableLayout datatable;
...   ...   ...


for (ParamValue param : params) {
TableRow row = new TableRow(this);
row.setLayoutParams(new LayoutParams(FILL_PARENT, WRAP_CONTENT));

TextView paramName = new TextView(this);
paramName.setText(param.parameter);
paramName.setLayoutParams(new LayoutParams(0, WRAP_CONTENT, 1));

TextView paramValue = new TextView(this);
paramValue.setText(param.value);
paramValue.setLayoutParams(new LayoutParams(0, WRAP_CONTENT, 1));

row.addView(paramName);
row.addView(paramValue);

datatable.addView(row, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
}





пятница, марта 09, 2012

FrameLayout и layout_marginTop

Не знаю, баг это или фича, но в случае, если view находится внутри frameLayout, то установка этому view свойства layout_marginTop ни на что не влияет - view по-прежнему располагается без отступа сверху.

Чтобы этот отступ сверху появился бы, нужно дополнительно задать этому view значение layout_gravity="top".

пятница, февраля 10, 2012

Пользователи phpbb3 не могут отправлять личные сообщения

Установил phpbb 3.0.10, создал пользователя и обнаружил, что ему не доступна возможность отправки личных сообщений:
Вам не разрешено использовать данную возможность. Возможно, вы недавно зарегистрировались на конференции или вам необходимо проявить больше активности на ней, чтобы получить такое право.
Обнаружил, что, начиная с 3.0.x (не помню с какой именно), появилась группа "Новые пользователи". Регистрируемые пользователи форума автоматически включаются в состав этой группы до тех пор, пока они не отправят заданное количество сообщений. Члены этой группы ограничены в возможностях отправки личных сообщений.

Количество сообщений задаётся в настройках: "Общие - Регистрация пользователей", пункт "Лимит сообщений для новых пользователей". По-умолчанию это значение установлено в 3. Если изменить его на 0, то новые пользователи в эту группу включаться не будут.



phpbb - Как настроить cookies для компьютера без имени домена

Понадобилось поставить phpbb3 на комп, для которого отсутствует доменное имя.
В общем случае phpbb в настройках кук такое не ожидает увидеть и в результате использует для отслеживания сессий не куки, а SID. Автоматический вход на форум в таком случае не работает. 

Вот здесь нашёл такое решение:
Пусть адрес форума имеет вид: http://mycomp/phpbb

В файле /includes/session.php нужно найти метод set_cookie и заменить в этом методе строку 'localhost' на имя компьютера 'mycomp'.

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

пятница, декабря 09, 2011

Oracle, как выбрать подмножество строк


Выбрать строки 10..20 из HR.departments

select * 
  from (select d.*, rownum rnum
          from HR.departments d
         where rownum <= 20)
where rnum >= 10

Если при этом набор данных нужно отсортировать, как чаще всего и бывает, то это выражение можно записать в виде

select * 
  from (select d.*, rownum rnum
          from HR.departments d
         where rownum <= 20
         order by d.department_name)
where rnum >= 10


но работать будет не правильно - значения rownum определяются на этапе выборки данных и при выполнении order by порядок будет утерян. 

Поэтому в таком случае можно обернуть выборку и сортировку в ещё один select:

select * 
from (
  select data.*, rownum rnum 
  from ( 
    select d.*
    from HR.departments d
    order by d.department_name ) data
  where rownum <= 20 )
where rnum >= 10


четверг, декабря 08, 2011

Cursor и порядок

Как (внезапно) выяснилось, порядок, в котором столбцы выбираются в курсор, имеет важное значение.

Пусть есть таблица:
CREATE TABLE A( F1 NUMBER(1), F2 FLOAT);

с такими данными:
INSERT INTO A VALUES(1, 11.1);  
INSERT INTO A VALUES(2, 22.22);
INSERT INTO A VALUES(3, 33.333);

И пусть мне захотелось сделать курсор, который бы содержал данные из этой таблицы:
DECLARE
  TYPE a_cursor_type IS REF CURSOR RETURN A%ROWTYPE;
  a_cursor a_cursor_type;
  
  a_record A%ROWTYPE;
BEGIN
  OPEN a_cursor FOR SELECT f1, f2 FROM A;
  
  LOOP
    FETCH a_cursor INTO a_record;
    EXIT WHEN a_cursor%NOTFOUND;
    
    dbms_output.put_line('F1: ' || a_record.f1 || ', F2: ' || a_record.f2);
  END LOOP;
END;

В выражении SELECT f1, f2 FROM A важно не перепутать порядок выбираемых полей таблицы - хотя в обычной жизни на такие вещи не обращаешь внимания. А вот с курсором обращать приходится, в противном случае вместо ожидаемого результата
F1: 1, F2: 11.1
F1: 2, F2: 22.22
F1: 3, F2: 33.333

легко получить
Error report:
ORA-06502: PL/SQL: numeric or value error: number precision too large
ORA-06512: at line 10
06502. 00000 -  "PL/SQL: numeric or value error%s"

При этом повезёт, если типы столбцов или содержащиеся в них значения окажутся не совместимые и сразу получим сообщение об ошибке. Иначе можно совсем долго разбираться, отчего в нужных столбцах показываются неправильные данные.




понедельник, декабря 05, 2011

Как подключиться из ERwin к Oracle

Предположим, я хочу выполнить Reverse Engineer для какой-то схемы базы. Для этого я вызываю Tools -> Reverse Engineer... Появляется диалог, в котором я указываю тип СУБД - Oracle


Нажимаю Next.

Изменяю или оставляю заданные значения опций импорта на появившемся окне:


и снова нажимаю Next.

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

Здесь я указываю тип аутентификации - у меня используется Database Authentification.
Указываю значения username и пароля. 

После этого необходимо указать значение строки соединения (connection string). Значение этой строки можно добыть из tnsnames.ora.

В tnsnames.ora выбираю определение

XE =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = desktop-1)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = XE)
    )
  )

Это выражение нужно привести к одной строке, убрав переводы строк, получив вот такое:

(DESCRIPTION =  (ADDRESS = (PROTOCOL = TCP)(HOST = desktop-1)(PORT = 1521)) (CONNECT_DATA =  (SERVER = DEDICATED) (SERVICE_NAME = XE)))

Копирую эту строку в поле ввода Connection String - и можно нажимать Connect. 



воскресенье, декабря 04, 2011

Поле для хранения GUID в Oracle, использование SYS_GUID

Тип данных для хранения значения - RAW(16).
Для автогенерации значения можно сделать триггер, использовать хранимую процедуру, а можно и вот так:

CREATE TABLE TEST(ID RAW(16) DEFAULT SYS_GUID(), ...);

С другой стороны, если хотим получить значение созданного идентификатора, как обычно и бывает, можно вот так

DECLARE guid RAW(16);
BEGIN
  guid := SYS_GUID();
  INSERT INTO TEST2(ID, NAME) VALUES(guid, 'ПЖиВ И медведи');
END;

среда, ноября 16, 2011

JMeter, MS SQL Server и JDBC-драйвер

Отчего-то не получилось сделать так, чтобы JMeter заработал бы с JDBC-драйвером от Microsoft. Пробовал с sqljdbc4.jar текущей версии (sqljdbc_3.0.1301.101_rus).

Вроде, на вид, всё хорошо - сделал совсем простой Thread Group, добавил JDBC Connection Configuration, указал нужные параметры... Добавил один JDBC Request с совсем простым запросом. Ну и, конечно, View Results Tree.

И что получилсь? Да ничего.

Нажимаю Run - Start, и сценарий сразу же молча завершается. В View Results Tree нет вообще ничего. В логе JMeter появляется только одна строка - "Running the test!". Пункт меню Run - Start снова горит как Enabled.

Для интереса пробую:

Изменяю значение jdbc connection. Было вот так: jdbc:sqlserver://localhost:1433;databaseName=MyDatabase

Добавляю в начале произвольный символ. Запускаю. В View Results Tree вижу корректное сообщение об ошибке, а в логе JMeter - соответствующий Exception.

Изменяю название базы данных на заведомо неправильное. И снова, ожидаемое сообщение об ошибке в View Results Tree, ожидаемое исключение в логе.


Изменяю название class name на заведомо неправильное, например, на такое com.microsoft.sqlserver.jdbc.SQLServerDriver123. Снова корректное сообщение об ошибке.


Указываю заведомо неправильное имя пользователя или неправильный пароль. Опять вижу ожидаемую ошибку.

Но стоит вернуть всё к правильным значениям - опять ничего не происходит.

Решил попробовать использовать другой драйвер. Скачал и поставил текущую сборку jTDS. Разумеется, что поменял название класса - стало net.sourceforge.jtds.jdbc.Driver и connection string - теперь она сделалось такой - jdbc:jtds:sqlserver://localhost:1433/MyDatabase.

И всё сразу получилось.

Если честно, так и не понял, чем драйвер от MS так не подошёл к JMeter.

понедельник, ноября 07, 2011

Ссылки на чтение про JMeter

Понадобилось поразбираться  в теме нагрузочного тестирования, поэтому решил пособирать ссылки на эту тему.

Простой нагрузочный тест с Apache JMeter
Описывается использование Access Log Sampler для создания сценария тестирования, средств отображения View Results in Table, Graph Results, Aggregate Report и подходы к интерпретации получаемых результатов.

Рецепт нагрузочного тестирования на JMeter
Так же на основе Access Log Sampler, но более уже более продвинутая логика тестирования.

Быстрое создание нагрузочных тестов на JMeter для web-сайтов
Описывается создание сценариев путём записи действий пользователя при помощи Recording Controller и HTTP Proxy Server. Так же показано использование CSV Data Set Config для получения логинов и паролей из CSV-файла для имитации авторизации разных пользователей.

Отладка тест-плана JMeter
Показывается отладка тестового плана, использование HTTP Request Defaults, Regular Expression Extractor, If Controller.

Apache JMeter
Ссылка на сайт проекта JMeter

SQL query performance test with JMeter
Конфигурация тестирования MS SQL Server

Regular Expressions in JMeter


Хорошая статья, очень хороша для начала работы. 


А вот тут, кстати, живёт набор плагинов для JMeter - jmeter-plugins

Easy scripting of JSON applications with Apache JMeter

четверг, октября 13, 2011

Как включить показ номеров строк в редакторе SQL Server Management Studio

Чтобы не отвечать на вопрос "А чем ты занимался вчера три часа" ответом "Искал строчку 983 в редакторе" удобно включить отображение номеров строк в редакторе.

Включить можно вот тут: выбрать Сервис - Параметры - Текстовый редактор - Все языки - Общее и поставить крыжик "Номера строк".

В англ. варианте путь этот - Tools - Options - TextEditor - All Languages - General.

среда, октября 12, 2011

Word 2010 - Макрос не найден. Возможно он отключен по соображениям безопасности

Запускаю свежеустановленный MS Office 2010 - и при запуске три раза появляется сообщение "Макрос не найден. Возможно он отключен по соображениям безопасности".



У меня Windows 7 64bit и такой же, 64-bit-ный, Office.

На компьютере установлен Lingvo X3, вот он-то и портит нам картину этим сообщением.
Что характерно, Office ставился уже после того, как был установлен Lingvo.

Чтобы убрать это надоедливое сообщение (пока в ABBYY не смогут пофиксить проблему, правда, зная сколько они делают Lingvo для Андроида, возможно этого не случится никогда), нужно:

зайти в %APPDATA%\Microsoft\Word\Startup и переименовать файл Lingvo14.dotm. Делать это нужно при не запущенном офисе.

понедельник, октября 10, 2011

ProgressDialog, Back и handler


У меня есть activity. На ней имеется кнопка, инициализирующая обращение к какому-то объекту, выполняющему длительную обработку запроса. Перед началом обработки activity показывает progressDialog.
В качестве одного из параметров метода вызываемого объекта передаётся handler. После окончания обработки запроса сервер отправит этому handler сообщение, уведомляя о завершении работы, и передавая, возможно, результат при помощи Bundle.

handler, получив сообщение, спрячет progressDialog и покажет alertDialog, который и сообщит о завершении обработки запроса.

Пусть логика взаимодействия подразумевает возможность не дожидаться завершения запроса. progressDialog в таком случае логично сделать сancelable.

Теперь нужно сделать так, чтобы при нажатии пользователем на Back во время отображения progressDialog, не только прятался бы сам progressDialog, но и завершалась бы текущая activity.  В противном случае получается так, что progressDialog больше не показывается, но по-прежнему продолжает показываться та же activity. Что смутительно.

Для этого нужно диалогу сказать, что делать в случае его отмены:


@Click
public void callServerButton() {
ProgressDialog dialog = new ProgressDialog(this);
dialog.setTitle("Подождите");
dialog.setMessage("Запрос обрабатывается...");
dialog.setCancelable(true);
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {

@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel();
finish();
return true;
}
return false;
}
});

activeDialog = dialog;
activeDialog.show();

Server server = new Server();
server.doIt(serverHandler);
}

Теперь во время отображения progressDialog в случае нажатия на Back закроется не только сам диалог, но и activity, чего и добивались.

Теперь возникает другой вопрос - пусть пользователь, не дождавшись завершения работы сервера, нажал Back, в результате activity скрылась. Пользователь продолжает работать с приложением. А в это время сервер закончил обработку запроса и отправил сообщение handler.

handler получает сообщение, пытается создать и отобразить alertDialog. Но диалог использует контекст той activity, которой уже нет - в результате получаем завершение работы приложения и stacktrace для дальнейшего изучения.

Чтобы избежать такой проблемы добавляем какой-нибудь признак в activity. Доопределяем onStop() так, чтобы при выполнении этого метода устанавливалось бы значение этого признака. После этого говорим handler - показывай AlertDialog только в том случае, если значение признака не установлено.

Разумеется, при таком решении пользователь лишается возможности узнать, чем же закончилось выполнение запроса. Для не критичных случаев - это нормально. Например, пользователь захотел посмотреть значения курсов валют, но, не дождавшись, передумал.

Если же происходит что-то, требующее уведомления пользователя, то в данном решении нужно посмотреть в сторону Toast или Notifications.

воскресенье, октября 09, 2011

Не удаётся установить MS SQL Server 2008R2

У меня стоял MS SQL Server 2008R2. Решил снести - снёс.
Решил поставить ещё раз. И вот тут возникла проблема - не встаёт.

Выглядит следующим образом:

я запускаю SQLEXPRWT_x64_RUS.exe, оно распаковывается.








Появляется окошко с доступными действиями.

Я из этого окошка выбираю "New Installation..."
Появляется вот такое окно:

























Прогресс-бар добирается почти до самого правого края - после чего это окошко пропадает и... и ничего больше не происходит. Совсем.

Пошёл читать логи установки (они находятся в C:\Program Files\Microsoft SQL Server\100\Setup Bootstrap\Log), пошёл в гугла.. Конкретно для этой ситуации решения не нашёл, но нашёл вот такую рекомендацию:

1. В реестре удалить ветки
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer


2. Отправиться в  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstal и там удалить все подпункты, в которых упоминается MS SQL Server


3. Пойти в (или на?!)  HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services, откуда удалить пункты, связанные с SQL Server.


4. Перезагрузиться и запустить инсталляцию ещё раз. 


И - помогло ведь. Удалил, перезагрузил, запустил - и пошла установка нормальным образом. 










вторник, сентября 20, 2011

Check Point SR_GUI - не показывается иконка в трее

Пропала иконка CheckPoint VPN Client из трея. При ручном запуске SR_GUI.exe ничего не происходит.

Вылечилось остановкой и запуском сервисов

  • Check Point VPN Securemote service
  • Check Point VPN Securemote watchdog

пятница, сентября 16, 2011

Показать изображение в TextView при помощи Html.ImageGetter


У меня есть экземпляр TextView и я хочу в нём показать картинку, которая хранится в drawables.
Пусть картинка эта - upd_inactive.png.
Для этого мне понадобится Html.ImageGetter

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
     
        ImageGetter imageGetter = new ImageGetter() {

@Override
public Drawable getDrawable(String source) {
Drawable drawable;

// можно вот так - через имя картинки
int path = TestAppActivity.this.getResources().getIdentifier(source, "drawable", "cv.testapp");
   drawable = TestAppActivity.this.getResources().getDrawable(path);
 
   // или вот так
   drawable = getResources().getDrawable(R.drawable.upd_inactive);
 
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
             
       return drawable;
   }
};

TextView imageView = (TextView) findViewById(R.id.imageView);
imageView.setText(Html.fromHtml("This is an image: <img src=\"upd_inactive\">", imageGetter, null));
    }

воскресенье, августа 28, 2011

Tomcat, Spring, Vaadin

Скопировал spring-instrument-tomcat-3.0.2.RELEASE.jar в server/lib (на самом деле конкретный релиз не важен, просто такой был у меня)

В META-INF/context.xml добавил

  <Context path="/my-context-path">
        <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
  </Context>

Вместо этого можно было бы в catalina.bat прописать так:
SET JAVA_OPTS=%JAVA_OPTS% -javaagent:"%CATALINA_HOME%\lib\spring-instrument-3.0.2.RELEASE.jar"

При этом в server/lib понадобился бы spring-instrument-tomcat-3.0.2.RELEASE.jar

В web.xml

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>


  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:app-config.xml</param-value>
  </context-param>


Конфигурируем класс vaadin-приложения

@Configurable(preConstruction = true)
public class MyApplication extends Application {

    @Autowired
    private MyService service;
 
    @Override
    public void init() {
Window mainWindow = new Window("MyApplication");                  
         ...   ...   ....
     
setMainWindow(mainWindow);              
    }
}

четверг, августа 11, 2011

Символ процента в строковых ресурсах Андроида

Как в строковый ресурс поместить символ процента так, чтобы при этом ничего бы не ругалось?

Например, хочу получить такую строку:
1% - один процент, 2% - два процента

Тогда в strings.xml у меня будет:

<string formatted="false" name="my_string">1% - один процент, 2% - два процента</string>


воскресенье, мая 15, 2011

Обработка нажатия Enter на виртуальной клавиатуре


View.OnKeyListener keyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_DOWN) {
обрабатываем-факт-нажатия

return true;
}

return false;
}
};

myEditText.setOnKeyListener(keyListener);

суббота, мая 07, 2011

Локаль для запуска Netbeans 7.0 или Netbeans по-английски

По-умолчанию Netbeans 7 запускается с русским вариантом интерфейса. Зрелище - для любознательного исследователя, ведь понять что делает, например, пункт меню "Закрепить код" - дано не каждому (в нормальном варианте это пункт Fix Code).

Чтобы запустить NB с привычным английским вариантом нужно указать дополнительно параметр командной строки
your-path\netbeans.exe --locale en

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

вторник, марта 15, 2011

Android SDK не находит JDK на Windows 7 64-bit

При попытке установить при помощи exe-установщика Android SDK r10 получил такое вот ругательство:

Java SE  Development Kit (JDK) not found.

ОС у меня Windows 7 64-bit. Если JDK не был бы установлен, я бы и не удивился, но в данном конкретном случае он установлен совершенно однозначно. (Кстати, а JDK - оно какого рода?)

В интернете нашёл неожиданное, но работающее решение: на окошке с сообщением о невозможности найти вначале нажать на Back, а потом, с предыдущего окна, снова на Next. В этом случае на отсутствие JDK не ругается. 

суббота, августа 07, 2010

Ubuntu Tweak

Я вот с Линухом не очень часто тружусь, поэтому кое-что постоянно забывается.
Прочитал про Ubuntu Tweak, удобная штука для таких, как я.

Ubuntu Tweak is an application to config Ubuntu easier for everyone.
It provides many useful desktop and system options that the default desktop environment doesn't provide.

понедельник, июля 26, 2010

Как получить курсор из хранимой процедуры Oracle - SimpleJdbcCall

Понадобилось получить набор данных из хранимой процедуры Oracle. Данные получить эти надо в java-приложение.
Посмотрел несколько вариантов, наиболее симпатичным (что, впрочем, предсказуемо) оказался вариант и использованием спрингового SimpleJdbcCall.

Итак, есть такая табличка - разумеется, что здесь всё упрощено насколько это можно - и даже чуть более

CREATE TABLE BOOK (
    ID INTEGER NOT NULL PRIMARY KEY, 
    NAME VARCHAR2(20))

Есть такая вот хранимая процедура

PROCEDURE GET_BOOKS_BY_NAME (book_cursor OUT SYS_REFCURSOR, name_template IN VARCHAR2)
IS
BEGIN
  OPEN book_cursor FOR
    SELECT b.ID as ID, b.NAME as NAME 
      FROM BOOK b
     WHERE b.NAME LIKE name_template
     ORDER BY ID;    
  
END GET_BOOKS_BY_NAME;

Осталась самая малость - получить от этой процедуры содержимое курсора book_cursor.

Для начала, понадобится получить экземпляр SimpleJdbcCall. Его можно создать руками, можно прописать в контексте спринга - по вкусу.

Для представления данных удобно создать отдельный DTO


public class BookItem {
    private Integer id;
    private String name;
    public BookItem() {}
    // getters и setters опущены 
}


На экземпляры этого класса будут отображены данные из получаемого курсора.

А теперь собираем всё вместе и получаем данные


    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("app-config.xml");


        SimpleJdbcCall simpleJdbcCall = context.getBean("simpleJdbcCall", SimpleJdbcCall.class);


        // Этот экземпляр отвечает за отображение набора данных, полученных из
        // базы данных в курсоре, на список объектов
        RowMapper rowMapper = ParameterizedBeanPropertyRowMapper.newInstance(BookItem.class);
        // Указывается имя вызываемой хранимой процедуры
        simpleJdbcCall.withProcedureName("GET_BOOKS_BY_NAME");
        // Связывается курсор, получаемый из хранимой процедуры, с отображателем
        // данных
        simpleJdbcCall.returningResultSet("BOOK_CURSOR", rowMapper);


        // Задаются входные параметры для хранимой процедуры
        SqlParameterSource params = new MapSqlParameterSource().addValue("NAME_TEMPLATE", "NEW%");


        // Теперь производится непосредственный вызов хранимой процедуры.
        // В возвращаемый в результате вызова map помещается имя курсора
        // (параметр типа String) и сами полученные данные в виде ArrayList.
        // Для того, чтобы эти данные из ArrayList разобрать, и используется
        // rowMapper
        Map resultMap = simpleJdbcCall.execute(params);


        // При этом вызове производится отображение полученных данных на тип,
        // указанный в параметре rowMapper
        @SuppressWarnings("unchecked")
        List resultList = (List) resultMap.get("BOOK_CURSOR");


        // А теперь просто работаем с тем, что получили от БД
        System.out.println("Получено записей: " + resultList.size());
        for (BookItem item : resultList) {
            System.out.println(item);
        }
    }

Вот и всё.

вторник, июля 06, 2010

Glassfish, Spring, и RAR5031:System Exception

Приложение имеет следующий простой вид: EJB SLSB фасад, который обращается с контексту Спринга, получает из него реализацию сервиса и делегирует вызов этом полученному сервису.

В ряде совершенно вроде бы безобидных случаев вываливался exception, начинающийся вот так:

RAR5031:System Exception.
java.lang.NullPointerException
at com.sun.enterprise.resource.ConnectorXAResource.getResourceHandle(ConnectorXAResource.java:228)
at com.sun.enterprise.resource.ConnectorXAResource.start(ConnectorXAResource.java:124)
at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.enlistResource(J2EETransactionManagerOpt.java:159)
at com.sun.enterprise.resource.ResourceManagerImpl.registerResource(ResourceManagerImpl.java:144)
at com.sun.enterprise.resource.ResourceManagerImpl.enlistResource(ResourceManagerImpl.java:102)
at com.sun.enterprise.resource.PoolManagerImpl.getResource(PoolManagerImpl.java:216)
at com.sun.enterprise.connectors.ConnectionManagerImpl.internalGetConnection(ConnectionManagerImpl.java:327)
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:189)
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:165)
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:158)
at com.sun.gjc.spi.base.DataSource.getConnection(DataSource.java:108)
at org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider.getConnection

Случаи были совсем безобидные - вида (автоматического, разумеется) обращения к БД для генерации идентификатора (через TableGenerator) или в случае вызова тразакционного спрингового компонента из транзакционного же EJB SLSB... Короче, во всех этих случаях ничего ломаться бы и не должно было - но ломалось.

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

RAR5031:System Exception.
java.lang.NullPointerException

RAR5031:System Exception.
com.sun.enterprise.resource.PoolingException: java.lang.NullPointerException
RAR5029:Unexpected exception while registering component.
java.lang.RuntimeException: Got exception during XAResource.start:
poolmgr.err_enlisting_res_in_getconn
RAR5117 : Failed to obtain/create connection from connection pool [ ]. Reason : java.lang.RuntimeException: Got exception during XAResource.start:
RAR5114 : Error allocating connection : [Error in allocating a connection. Cause: java.lang.RuntimeException: Got exception during XAResource.start:]

--In J2EETransaction.rollback, jtsTx=null nonXAResource=null
ejbDestroyed: BookServiceBean; id: [B@159b1a0
--In J2EETransaction.rollback, jtsTx=null nonXAResource=75
context with empty container in ContainerSynchronization.afterCompletion
EJB5018: An exception was thrown during an ejb invocation on [BookServiceBean]

javax.ejb.EJBException
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: java.sql.SQLException: Error in allocating a connection. Cause: java.lang.RuntimeException: Got exception during XAResource.start:
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: java.sql.SQLException: Error in allocating a connection. Cause: java.lang.RuntimeException: Got exception during XAResource.start:
Caused by: java.rmi.RemoteException: null; nested exception is:
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: org.hibernate.exception.GenericJDBCException: Cannot open connection
Caused by: java.sql.SQLException: Error in allocating a connection. Cause: java.lang.RuntimeException: Got exception during XAResource.start:
javax.ejb.EJBException: nested exception is: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.RemoteException: null; nested exception is:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection

Проблема оказалась в следующем: не было установлено свойство allow-non-component-callers в описании пула соединений. После того, как свойство это установил - все проблемы кончились.

К слову, документация по этому поводу сообщает:
You can allow non-Java-EE components, such as servlet filters and third party persistence managers, to use this JDBC connection pool. The returned connection is automatically enlisted with the transaction context obtained from the transaction manager. Standard Java EE components can also use such pools. Connections obtained by non-component callers are not automatically closed at the end of a transaction by the container. They must be explicitly closed by the caller.

суббота, мая 08, 2010

Извращённые способы перевода

Я не понимаю, как наши переводчики умудряются так работать.

В переводе "Grails in Action" получил название "Grails. Гибкость Groovy и надежность Java".
Вместо того, чтобы по возможности максимально подчеркнуть тот факт, что основа для перевода относится к уважаемой manning-овской серии ... In Action и обозвать как-нибудь "Grails в действии" - замутили что-то непонятное, непотребное.

четверг, мая 06, 2010

Mindmapping - обзор, советы и инструменты

Обзор разных тулзов для делания mindmapping-диаграмм, советы по организации, построению, использованию и другим прочим полезным вещам.
Bulletproof of Mind Mapping: Overview, Benefits, Tips and Tools

вторник, апреля 20, 2010

Apache Commons Lang - ToStringBuilder, формирование строки и форматы

Для определённости, возьём такой простой класс:

class Book {
    private String title;
    private String author;
    private Double price;
    private Date   publDate; 
    ... 
}


Я хочу для этого класса получить toString-like отображение.
Удобный для этого способ - взять и применить ToStringBuilder из библиотеки apache commons lang.

Полное имя этого класса -  org.apache.commons.lang.builder.ToStringBuilder.

Способ №1 - руками переопределить метод toString и, с помощью этого класса, описать способ конструирования строкового отображения объектов. Ничего особо примечательного здесь нет, за исключением удобства, которое добавляет упомянутый ToStringBuilder.

    @Override
    public String toString() {
        return new ToStringBuilder(this)
          .append("title", title)
          .append("author", author)
          .append("price", price)
          .append("date", publDate)
          .toString();
      } 


А вот вид получаемой строки зависит от заданного на момент вызова стиля (формата). Примеры этих форматов - чуть низже.

Способ №2 интереснее - он использует reflection. В этом случае мне достаточно вызвать ToStringBuilder.reflectionToString(мой-объект), не занимаясь перечислением имён полей и их значений вручную.

Например, для такого случая

  Book book = new Book();
  book.setAuthor("Gregory M.");
  book.setTitle("The Secret Book of Secrets");
  book.setPrice(15.03);
  book.setPublDate(null); // это специально, чтобы явно показать null-значение


вызов

  System.out.println(ToStringBuilder.reflectionToString(book));

вернёт такой вот результат:
>>> javaapplication243.Book@10b30a7[title=The Secret Book of Secrets,author=Gregory M.,price=15.03,publDate=] 

Да, вот эти >>> я добавил в результат сам, для удобства (моего) при чтении.

Есть несколько способов повлиять на формат получаемого результата.
Результат, показанный чуть выше, получен при использовании формата, используемого по-умолчанию. Имя этого формата - DEFAULT_STYLE.

Можно руками, вызвав статический метод, указать формат, который я хочу использовать:
  
  ToStringBuilder.setDefaultStyle(ToStringStyle.MULTI_LINE_STYLE);

А можно указать нужный формат явным образом, например, вот так:

  ToStringBuilder.reflectionToString(book, ToStringStyle.MULTI_LINE_STYLE)      

Теперь самое интересное - какие форматы есть и как они влияют на результат.

DEFAULT_STYLE
>>> javaapplication243.Book@10b30a7[title=The Secret Book of Secrets,author=Gregory M.,price=15.03,publDate=]  

MULTI_LINE_STYLE
>>> javaapplication243.Book@10b30a7[
      title=The Secret Book of Secrets
      author=Gregory M.
      price=15.03
      date=
    ]


SHORT_PREFIX_STYLE
>>> Book[title=The Secret Book of Secrets,author=Gregory M.,price=15.03,publDate=]

NO_FIELD_NAMES_STYLE
>>> javaapplication243.Book@10b30a7[The Secret Book of Secrets,Gregory M.,15.03,]

SIMPLE_STYLE
>>> The Secret Book of Secrets,Gregory M.,15.03,         

среда, апреля 14, 2010

Составной первичный ключ из двух FK

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





В EVENT_KIND содержится список каких-то видов событий, а в VALID_EVENT_KIND - список допустимых сочетаний "предыдущий вид события" - "следующий тип события".

Если работа с EVENT_KIND типична и ничего интересного не представляет, то вот случай с VALID_EVENT_KIND - интереснее.
Классогенератор Netbeans отказыватеся генерировать для этой таблицы соответствующий Entity, резонно, в некотором смысле, сообщая о том, что это ведь обычная таблица связи (для отношения многие-ко-многим), а поэтому и сущность соответствующая не нужна. А раз не нужна, то и делать не буду (упорный, блин).

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

Итак, раз для нас код сгенерировать отказались, остаётся написать его самому. К счастью, особо тут и писать нечего.

Вот так выглядит класс, соответствующий VALID_EVENT_KIND

@Entity
@Table(name="VALID_EVENT_KIND")
public class ValidEventKind implements Serializable {

    @EmbeddedId
    private ValidEventKindPk pk;

    public ValidEventKind() {}

    public ValidEventKind(EventKind prevEvent, EventKind nextEvent) {
        this.pk = new ValidEventKindPk(prevEvent, nextEvent);
    }
}
Ничего интересного, за исключением @EmbeddedId.
А вот и описание самого первичного, embedded-ного, ключа:

@Embeddable
public class ValidEventKindPk implements Serializable {
    @ManyToOne()
    @JoinColumn(name="prev_event", referencedColumnName="code")
    private EventKind prevEventKind;

    @ManyToOne
    @JoinColumn(name="next_event", referencedColumnName="code")
    private EventKind nextEventkind;

    public ValidEventKindPk() { }

    public ValidEventKindPk(EventKind prevEventKind, EventKind nextEventKind) {
        this.prevEventKind = prevEventKind;
        this.nextEventkind = nextEventKind;
    }
}
Разные getters и setters в обоих классах я, очевидное дело, опустил.

Вот и всё...

среда, апреля 07, 2010

Grails - generate-controller и ошибка 404

Создал новое приложение.
Добавил domain-класс Album.
Добавил контроллер, установил ему scaffold.
Всё работает. Приложение запускается, ссылка на контроллер показывается, переход по ссылке переходится.

Теперь я не хочу больше scaffold. Я говорю: а сделай мне generate-controller. Делает.

Запускаю приложение снова. Кликаю по ссылке на контроллер и наблюдаю ошибку:

HTTP Status 404 - /GrailsApplication2/WEB-INF/grails-app/views/album/list.jsp
type Status report
message /GrailsApplication2/WEB-INF/grails-app/views/album/list.jsp
description The requested resource (/GrailsApplication2/WEB-INF/grails-app/views/album/list.jsp) is not available.

Вот этот самый jsp, если честно, меня озадачил.

В итоге определилась причина пробелмы: не были сгенерированы представления (view по-нашему).
Поэтому говорю: generate-views для своего класса - и всё становится ОК.

Netbeans, пусть к JDK

Уже не в первый раз после переустановки JDK соображал, как указать Netbeans на новое её местоположение. Дело в том, что при загрузке он ищет её (или его?) по старому месту, и, не обнаружив, ругается.

Так как каждый раз разбираться заново мне надоело, решил записать.

Путь к JDK указывается в netbeans.conf, который живёт в каталоге INSTALL_DIR/etс:

netbeans_jdkhome="D:\DevTools\Java\jdk1.6.0_19"

Grails - ошибка в синтаксисе команды

Запускаю из коммандной строки grails - получаю сообщение "Ошибка в синтаксисе команды".
Подумал - дистрибутив неправильный может? Нет, нормальный, нетбинзу нравится.

Оказалось: не установлен GRAILS_HOME.

Установил - заработало.

суббота, февраля 20, 2010

Обновился список из Top 25 Most Dangerous Programming Errors

17 февраля обновился список 25 самых опасных ошибок в софтостроении:
2010 CWE/SANS Top 25 Most Dangerous Programming Errors

Вот эти вещи признаны самыми опасными:
[1] Failure to Preserve Web Page Structure ('Cross-site Scripting')
[2] Improper Sanitization of Special Elements used in an SQL Command ('SQL Injection')
[3] Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
[4] Cross-Site Request Forgery (CSRF)
[5] Improper Access Control (Authorization)
[6] Reliance on Untrusted Inputs in a Security Decision
[7] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
[8] Unrestricted Upload of File with Dangerous Type
[9] Improper Sanitization of Special Elements used in an OS Command ('OS Command Injection')
[10] Missing Encryption of Sensitive Data
[11] Use of Hard-coded Credentials
[12] Buffer Access with Incorrect Length Value
[13] Improper Control of Filename for Include/Require Statement in PHP Program ('PHP File Inclusion')
[14] Improper Validation of Array Index
[15] Improper Check for Unusual or Exceptional Conditions
[16] Information Exposure Through an Error Message
[17] Integer Overflow or Wraparound
[18] Incorrect Calculation of Buffer Size
[19] Missing Authentication for Critical Function
[20] Download of Code Without Integrity Check
[21] Incorrect Permission Assignment for Critical Resource
[22] Allocation of Resources Without Limits or Throttling
[23] URL Redirection to Untrusted Site ('Open Redirect')
[24] Use of a Broken or Risky Cryptographic Algorithm
[25] Race Condition

среда, октября 28, 2009

воскресенье, мая 03, 2009

Фотки

Я обучился выкладывать фотки на фликер :)
Точнее говоря не то чтобы обучился, но на ADSL это стало уже возможным.

Тут пара фоток с воскресной поездки в сад - это как майская природа на Урале выглядит. Снега, кстати, уже не очень много http://tinyurl.com/cpa7na

А тут фотки с прошлой осени, из Минска http://tinyurl.com/c8bv8h

Ссылки, кстати, пожаты через зело полезный сервис http://tinyurl.com

воскресенье, марта 29, 2009

Исходящий трафик на порт 5431 маршрутизатора

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

Роутер - D-Link 2640, хотя это и не принципиально.

Симптоматика была такая - заметил я в графике, который рисует NetWrox, стабильный поток исходящих данных в районе 2КБ/с.

При этом я помню: (а) раньше такого не было, (б) помнится, что-то я делал с сетевыми подключениями. Что именно делал, разумеется, не помню. Я, конечно, знаю, что надо записывать, только всегда об этом забываю.

Аутпост рисует следующую картинку:
1. стабильно исходящий трафик на 192.168.1.1:5431
2. при ручном разрыве этого соединения сразу же образуется другое, правда, с другим номером локального порта
3. трафик исходит от процесса svchost.exe - от того его экземпляра, к которому подцеплена куча служб, таких как диспетчер аудио, служба автоподключний, служба COM+
4. в http-журнале куча (по нескольку за одну секунду) обращений от svchost по такому адресу: 192.168.1.1:5431/uuid:0000e018-d0a0-00e0-80a0-489801e82808/WANCommonInterfaceConfig:1
5. в журнале пакетов много-много-много подобных событий:
23:09:52 Разрешить IN TCP 192.168.1.1 5431 192.168.1.2 1180 PSH ACK ~Автоматически созданное правило для соединения
23:09:52 Разрешить OUT TCP 192.168.1.2 1180 192.168.1.1 5431 ACK ~Автоматически созданное правило для соединения

После неоднократной проверки системы разными антивирусами и трояноискателями я сказал "мы пойдём другим путём!" и стал вспоминать, что же менял в настройках подключений.

Зашёл туда - увидел слово "Шлюз". Вот тут-то собака, оказывается, и порылась:
1. Выбираю Подключение к интернету из категории "Шлюз интернета"
2. Открываю Свойства
3. Снимаю крыжик "При подключении вывести значок в области уведомлений" - готово!
И пропал этот трафик, как и не было.

суббота, сентября 20, 2008

Что может быть дороже для русского человека чем морковь?! Только морковь, заботливо собранная инженерами третьей, второй и первой категорий!
Posted by Picasa
А это специально обученный комбайн, предназначенный для помощи инженерам в уборке морковки.
Posted by Picasa

Есть такой род деятельности (инженерной) - помощь работником агрофермы в уборке морковки. Потому как совка уж и нет, но дело его - живёт.
И вот на поле этой самой агрофермы обнаружил я такой девайс. Очень готичный.
Posted by Picasa

пятница, февраля 22, 2008

Классные картинки

Не знаю, кому как, а мне такие картинки сильно нравятся:


А ещё их есть по такому адресу: http://ekabu.ru/fun/5516-risunki.html

Но вообще, это переводы оригинальных мультов с http://www.nichtlustig.de. Там правда по-немецки, ну да и ладно. Я вот его немножко ещё помню :)

среда, февраля 13, 2008

Наша почта

Нет, на самом деле, наша почта - это что-то.

Я на работе, звонит жена из дому. Говорит, звонили с почты - пришла какая-то недорогая посылка, но в ящик почтовый не влезла. Поэтому просят зайти, забрать.

По пути с работы заезжаю на почту. Дают мне - не паспорта ни спросив - посылку.
В посылке - две книжки, которые я на Амазоне заказывал. $95 за обе.

Вот и не могу я понять - сто баксов - оно для почтовых работников не деньги? :)

Кстати, книжки дошли достаточно быстро, около месяца из Америки добирались. Судя по штепелю, через Германию.

В любом случае рад, что они позвонили. До этого заказывал тоже на Амазоне книгу. Через полгода с Амазона пришло письмо - посылка ваша к нам вернулась в связи с недоставленностью получателю, деньги за неё перевели обратно на карточку. Судя по всему, в прошлый раз почтовикам звонить было лень...

среда, ноября 28, 2007

Берн и Фрибур

Ура, я добрался до своих фотографий из Европы. На этот раз очередь дошла до Берна и Фрибура.

Если вместо русских букв кракозябры - UTF-8 знает, как помочь.

суббота, ноября 17, 2007

Свидетели плана

Цитата:

На Манежной площади несколько человек выстроились в ряд и начали
поклоняться большой растяжке "Москва голосует за Путина", таким
образом, они объявили о создании движения "Свидетели Плана Путина".
http://txt.newsru.com/russia/17nov2007/asd.html

Интересно, записаться туда можно? Весь город завесили уже растяжками и
плакатами.
Как-то сразу и вспоминается: "Старушка убивается, плачет, никак не
поймет что значит, на что такой плакат, такой огромный лоскут..."

среда, октября 03, 2007

Картинки из поездки

Я начинаю (потихоньку) выкладывать фотографии из катаний по европе.

1. Это проездом через Польшу с недолгим гулянием по Вроцлаву.
2. Тут картинки из Дрездена.
3. А здесь фотографии из Вадуца (Вадуц - столица Лихтенштейна (Лихнтенштейн - это княжество с князем и с замком князя))
4. Это из Швейцарии - Лугано, Люцерн и гора Пилатус

Картинка

Попробовал отправить картинку с телефона.

четверг, марта 15, 2007

среда, марта 14, 2007

Почему пиво в банках лучше, чем в бутылках

Рассказали такой вот случай из жизни в нашем городе.

Остановка, рядом с ней киоск-магазинчик. В него заходит пара - мужик с подругой, выходят с двумя банками пива и с пакетом орешков. Банки несёт подруга, мужик - фисташки.
Пара чуть отходит, дама подскальзывается, падает, лежит.
Мужик оборачивается, подходит к ней и говорит - "Видишь, как хорошо, что пиво в банках купили!" :))