<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Грамант</title>
	<atom:link href="http://blog.gramant.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.gramant.ru</link>
	<description>Корпоративный блог компании Грамант</description>
	<lastBuildDate>Thu, 01 Mar 2012 12:34:53 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Тестируем в браузере с помощью Geb</title>
		<link>http://blog.gramant.ru/2012/03/01/in-browser-testing-with-geb/</link>
		<comments>http://blog.gramant.ru/2012/03/01/in-browser-testing-with-geb/#comments</comments>
		<pubDate>Thu, 01 Mar 2012 08:22:21 +0000</pubDate>
		<dc:creator>Сергей Нековаль</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[geb]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[selenium]]></category>
		<category><![CDATA[spock]]></category>
		<category><![CDATA[WebDriver]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=957</guid>
		<description><![CDATA[Geb на практике Я вот, скажем, люблю, когда всю работу за меня делают роботы. Поэтому считаю необходимым всякие скрипты, inspections, проверщики орфографии и, разумеется, автоматические тесты. Кстати, как вам такой тестик: Browser.drive&#40;driver: new InternetExplorerDriver&#40;&#41;&#41; &#123; &#160; go &#34;http://www.google.com&#34; &#160; $&#40;'form', action:endsWith&#40;'/search'&#41;&#41;.q = 'тестирование при помощи geb и spock' $&#40;'button', value:'Поиск'&#41;.click&#40;&#41; &#160; waitFor &#123; $&#40;'#search'&#41; &#125; [...]]]></description>
			<content:encoded><![CDATA[<style>
table {
    border: none;
    padding: 0;
    border-collapse: collapse;
    margin-top: 5px;
}
table tr td, table tr th {
    margin: 0;
    padding: 5px;
    border: 1px solid #cecece;
}
table tr th {
    background-color: #f0f0f0;
}
</style>
<h2>Geb на практике</h2>
<p>Я вот, скажем, люблю, когда всю работу за меня делают роботы. Поэтому считаю необходимым всякие скрипты, inspections, проверщики орфографии и, разумеется, автоматические тесты. <span id="more-957"></span>Кстати, как вам такой тестик:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">drive</span><span style="color: #66cc66;color: #CCC;">&#40;</span>driver: <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> InternetExplorerDriver<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
&nbsp;
    go <span style="color: #ff0000;color: #666666;">&quot;http://www.google.com&quot;</span>
&nbsp;
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'form'</span>, action:endsWith<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'/search'</span><span style="color: #66cc66;color: #CCC;">&#41;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">q</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #ff0000;color: #666666;">'тестирование при помощи geb и spock'</span>
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'button'</span>, value:<span style="color: #ff0000;color: #666666;">'Поиск'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">click</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
    waitFor <span style="color: #66cc66;color: #CCC;">&#123;</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#search'</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;color: #577A61;">assert</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#search'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">size</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">==</span> <span style="color: #cc66cc;color: #DDD;">1</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">assert</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#search'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">find</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'li.g a.l'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">size</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&gt;</span> <span style="color: #cc66cc;color: #DDD;">0</span>
    <span style="color: #993399;">println</span> <span style="color: #ff0000;color: #666666;">&quot;Первый результат: &quot;</span> <span style="color: #66cc66;color: #CCC;">+</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#res'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">find</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'li.g a.l'</span>, <span style="color: #cc66cc;color: #DDD;">0</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">text</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
<span style="color: #66cc66;color: #CCC;">&#125;</span>.<span style="color: #006600;">quit</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span></pre></div></div>

<p>Мне кажется, у таких тестов высокая степень читаемости &#8211; неважно даже, какой это язык. Можно по такому вот образцу написать еще несколько подобных же тестов, не имея вообще никакого понятия о Geb, Groovy и о том, как это работает. Но для полного понимания немного углубимся в основы.<br />
<br/></p>
<h2>Про Geb и Selenium</h2>
<p>Кто в наши дни не слышал про <a href="http://seleniumhq.org/">Selenium</a>? Наверное, тот, кто ещё пороху не нюхал и до сих пор думает, что все браузеры одинаковы. Долго я про это распространяться не буду, достаточно лишь знать:</p>
<ul>
<li>Selenium позволяет запускать автоматические тесты прямо в браузере, воспроизводя всевозможные глюки и особенности.</li>
<li>Можно делать ВСЁ то, что делает обычный пользователь, а именно набирать текст, скроллить, двигать мышкой и т.п. При этом внимательно наблюдая за поведением страницы в браузере.</li>
</ul>
<p>Почему всё это так актуально? В современных веб-приложениях неимоверное количество JavaScript-кода, спецэффектов, анимации, AJAX и прочих прелестей. Никаких нормальных инструментов, позволяющих контролировать качество <em>без браузера</em> в общем-то и нет.</p>
<p>Фактически Selenium &#8211; это полноценный робот для тестирования в условиях, максимально приближенных к реальности. (Исторически можно еще припомнить Rational Robot, который был заточен под Internet Explorer и имел довольно скудные возможности по взаимодействию со страницей.)</p>
<p>Сам Selenium предоставляет несколько вариантов написания тестов:</p>
<ol>
<li>С помощью JavaScript. Тесты запускаются прямо в браузере и имеют прямой доступ к загруженной веб-странице, что позволяет им совершать любые манипуляции.</li>
<li>Через управляющий сервер. В отдельном процессе запускается тестовый код на произвольном языке. Этот код вызывает специальную программу &#8211; WebDriver &#8211; которая передает команды браузеру и позволяет контролировать состояние страницы.</li>
</ol>
<p>Далеко не все любят писать тесты на JavaScript, поэтому второй способ &#8211; WebDriver &#8211; получает все большее распространение. Он пригоден для разработки тестов на любом языке, удобен еще и тем, что позволяет выполнять тесты на другой машине.</p>
<p>Итак, <a href="http://gebish.org">Geb</a> &#8211; это очередное средство написания тестов, использующее WebDriver и основанное на языке Groovy. Использовать Geb внутри Groovy-скрипта можно с помощью следующих волшебных слов:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">@Grab<span style="color: #66cc66;color: #CCC;">&#40;</span>group<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'org.codehaus.geb'</span>, module<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'geb-core'</span>, version<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'0.6.2'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
@Grab<span style="color: #66cc66;color: #CCC;">&#40;</span>group<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'org.seleniumhq.selenium'</span>, module<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'selenium-api'</span>, version<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'2.14.0'</span><span style="color: #66cc66;color: #CCC;">&#41;</span> 
@Grab<span style="color: #66cc66;color: #CCC;">&#40;</span>group<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'org.seleniumhq.selenium'</span>, module<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'selenium-firefox-driver'</span>, version<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'2.19.0'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
@Grab<span style="color: #66cc66;color: #CCC;">&#40;</span>group<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'org.seleniumhq.selenium'</span>, module<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'selenium-ie-driver'</span>, version<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'2.19.0'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;color: #577A61;">import</span> <span style="color: #a1a100;">geb.Browser</span>
<span style="color: #000000; font-weight: bold;color: #577A61;">import</span> <span style="color: #a1a100;">org.openqa.selenium.firefox.FirefoxDriver</span>
...</pre></div></div>

<p><br/></p>
<h2>Язык Geb</h2>
<p>Одна из особенностей Groovy (как и всех динамических языков) в том, что его можно легко переделать в другой совершенно неузнаваемый язык &#8211; под любые нужды.</p>
<p>В нашем случае Geb предоставляет язык управления браузером. Можно совершать все, что умеет человеческий пользователь:</p>
<ul>
<li>Переходить по URL</li>
<li>Заполнять формы</li>
<li>Двигать мышкой</li>
<li>Кликать по ссылке</li>
<li>Взаимодействовать с разного рода popup-окнами и фреймами.</li>
</ul>
<p>Пример:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">drive</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    go <span style="color: #ff0000;color: #666666;">&quot;http://www.gramant.ru&quot;</span>
&nbsp;
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'.block .caption'</span>, text: <span style="color: #ff0000;color: #666666;">'Grails'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">closest</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'.block'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">find</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'a'</span>, text: startsWith<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'Подробнее'</span><span style="color: #66cc66;color: #CCC;">&#41;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">click</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">assert</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'title'</span>, text:<span style="color: #ff0000;color: #666666;">'Grails | Gramant'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">size</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">==</span> <span style="color: #cc66cc;color: #DDD;">1</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Как видим, Geb позволяет использовать синтаксис, напоминающий jQuery (но не совпадающий с ним полностью). Это удобно для нахождения нужных элементов в странице. Метод <strong>click()</strong> позволяет кликать на DOM-элементы, операция <strong>&lt;&lt;</strong> &#8211; отправлять браузеру текст, и так далее.</p>
<p>Для навигации этого достаточно. Правда, человек еще умеет читать информацию на странице. Для этого ему хорошо бы сначала понимать, на какой странице он сейчас находится. Давайте подумаем для начала, что вообще такое <em>страница</em> в тестировании.<br />
<br/></p>
<h2>Страницы и модули</h2>
<p>Мы скомандовали <code>go 'http://www.gramant.com'</code> и перешли на страницу <code>gramant.com</code>. Теперь можно эту страницу &#8220;прочитать&#8221;, т.е. проанализировать и найти ошибки. Так? На самом деле нет. При попадании браузера на страницу может случится:</p>
<ul>
<li>HTTP redirect</li>
<li>Сработает какой-нибудь загадочный javascript-код и перенаправит вас на другую страницу</li>
</ul>
<p>Мы думаем, что находимся на одной странице, а оказались на другой. Дальше тесты пойдут <em>неправильным путем</em>. Поэтому очень важно понимать, на какой <strong>логической</strong> странице мы находимся.</p>
<p>Geb предлагает использовать логические страницы (Page) и так называемые модули (Module). Попытаемся это проиллюстрировать следующим примером:</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/geb-pages.png" alt="Страницы м модули Geb" title="Страницы и модули Geb" width="600" height="532" class="size-full wp-image-975" /></p>
<p>Зачем вообще нужна такая абстракция, как <strong>страница</strong>? Казалось бы, достаточно посмотреть текущий URL и мы все узнаем. Например, так:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #000000; font-weight: bold;color: #577A61;">class</span> SearchResultsPage <span style="color: #000000; font-weight: bold;color: #577A61;">extends</span> Page <span style="color: #66cc66;color: #CCC;">&#123;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">static</span> url <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #ff0000;color: #666666;">&quot;/yandsearch&quot;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Но бывает, что одна и та же <em>логическая</em> страница может иметь множество разных URL и состояний, причем не всегда правильно определять страницу по URL (который может непредсказуемо измениться). Можно это делать, например, по заголовку:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #000000; font-weight: bold;color: #577A61;">class</span> SearchResultsPage <span style="color: #000000; font-weight: bold;color: #577A61;">extends</span> Page <span style="color: #66cc66;color: #CCC;">&#123;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">static</span> at <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'title'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">text</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">==</span>~ <span style="color: #ff0000;color: #666666;">'.*Яндекс: Нашлось .* ответов'</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Возможны и другие варианты определения текущей страницы; как видим, все это сильно зависит от приложения. Иногда множество разных URL соответствуют одной и той же странице; иногда по одному и тому же URL в зависимости от внутреннего контекста может появиться несколько разных страниц. В общем случае псевдо-метод <code>at()</code> содержит логику определения того, находится ли сейчас браузер на данной странице.</p>
<p>Несколько слов про модули. На диаграмме видно, что <em>модуль</em> &#8211; это часть страницы (блок), причем есть модули, которые присутствуют на нескольких страницах. Например, поисковая строка Яндекса доступна как на портальной странице, так и над результатами поиска. Смысл модулей &#8211; повторное использование тестирующего кода.</p>
<p>Модули можно определить, например, так:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #000000; font-weight: bold;color: #577A61;">class</span> LoginModule <span style="color: #000000; font-weight: bold;color: #577A61;">extends</span> Module <span style="color: #66cc66;color: #CCC;">&#123;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">static</span> content <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        username <span style="color: #66cc66;color: #CCC;">&#123;</span><span style="color: #66cc66;color: #CCC;">&#125;</span>
        password <span style="color: #66cc66;color: #CCC;">&#123;</span><span style="color: #66cc66;color: #CCC;">&#125;</span>
        loginButton <span style="color: #66cc66;color: #CCC;">&#123;</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;input&quot;</span>, type: <span style="color: #ff0000;color: #666666;">&quot;submit&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#125;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>И затем использовать их таким образом:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #000000; font-weight: bold;color: #577A61;">class</span> HomePage <span style="color: #000000; font-weight: bold;color: #577A61;">extends</span> Page <span style="color: #66cc66;color: #CCC;">&#123;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">static</span> content <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        login <span style="color: #66cc66;color: #CCC;">&#123;</span> module LoginModule <span style="color: #66cc66;color: #CCC;">&#125;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
Browser.<span style="color: #006600;">drive</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    to HomePage
    login.<span style="color: #006600;">username</span> <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> <span style="color: #ff0000;color: #666666;">'user'</span>
    login.<span style="color: #006600;">password</span> <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> <span style="color: #ff0000;color: #666666;">'password'</span>
    login.<span style="color: #006600;">loginButton</span>.<span style="color: #006600;">click</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Существует, разумеется, возможность, определять много экземпляров модуля внутри страницы &#8211; это актуально для разного рода списков. Например, результаты поиска &#8211; каждый результат можно объявить модулем. Это делается с использованием конструкции <strong>moduleList</strong>, на которой я останавливаться не буду.<br />
<br/></p>
<h2>AJAX и все, все, все</h2>
<p>В теперешние времена внутри браузера может происходить множество удивительных вещей:</p>
<ul>
<li>AJAX-запросы</li>
<li>Анимация</li>
<li>Drag &#038; Drop</li>
<li>Popup-окна разного вида</li>
</ul>
<p>Тестировать такие штуки автоматически можно только при помощи in-browser тестов. Geb предлагает несколько инструментов:</p>
<h3>Доступ к переменным JavaScript</h3>
<p>Через объект <code>js</code> можно обратиться к значению <em>глобальных</em> переменных JavaScript:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">drive</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">assert</span> js.<span style="color: #006600;">myGlobalVar</span> <span style="color: #66cc66;color: #CCC;">==</span> <span style="color: #cc66cc;color: #DDD;">1</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Можно также пользоваться <em>глобальными</em> функциями:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">drive</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    js.<span style="color: #006600;">globalCall</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">assert</span> js.<span style="color: #006600;">globalFunc</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">==</span> <span style="color: #cc66cc;color: #DDD;">1</span>
    js.<span style="color: #ff0000;color: #666666;">&quot;document.write&quot;</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;go geb!&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    js.<span style="color: #006600;">exec</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;return document.location.href&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">==</span> <span style="color: #ff0000;color: #666666;">'http://www.gramant.ru'</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Этим механизмом я пользовался мало, поэтому предполагаю, что Geb будет стараться правильно сконвертировать все типы данных JavaScript (как это описано <a href="http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript%28java.lang.String,%20java.lang.Object[]%29">здесь</a>) в Groovy-типы, но зуб не даю.</p>
<p>Для эмуляции мышиных событий удобно использовать jQuery. Geb предоставляет специальное свойство jQuery для такого рода вызовов:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">drive</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;div#a&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">jquery</span>.<span style="color: #006600;">mouseover</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Эта строчка будет работать <strong>только</strong> при условии, что на тестируемой странице загружена jQuery версии 1.4 и выше. Фактически такой код транслируется в <code>js.exec()</code>.</p>
<h3>Условие ожидания</h3>
<p>Подождать чего-то: или установки каких-то JavaScript-переменных, или появления на странице определенной информации можно с помощью конструкции <code>waitFor</code>.<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Browser.<span style="color: #006600;">driver</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    go <span style="color: #ff0000;color: #666666;">'http://www.youtube.com/watch?v=8d1hp8n1stA'</span>
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'button#watch-share'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">click</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    waitFor <span style="color: #66cc66;color: #CCC;">&#123;</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#watch-actions-share'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">displayed</span> <span style="color: #66cc66;color: #CCC;">&#125;</span>
    $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#watch-actions-share'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #663399;">find</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'button.share-panel-embed'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">click</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    waitFor <span style="color: #66cc66;color: #CCC;">&#123;</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'textarea.share-embed-code'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">displayed</span> <span style="color: #66cc66;color: #CCC;">&#125;</span>
    <span style="color: #993399;">println</span> <span style="color: #ff0000;color: #666666;">&quot;Embed code для этого видео: &quot;</span> <span style="color: #66cc66;color: #CCC;">+</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'textarea.share-embed-code'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">value</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Этот скрипт получает embed code для ролика YouTube, нажимая несколько кнопок и ожидая завершения анимации.</p>
<p>Разумеется, вечно метод <code>waitFor</code> ждать не будет и по достижении некоторого таймаута рухнет, прервав тест. По умолчанию это 5 секунд.</p>
<h3>Использование WebElement и Actions</h3>
<p>Мы упомянули про Drag &#038; Drop. На данный момент (версия 0.6.2) Geb не имеет удобной абстракции для осуществления таких операций. Тем не менее, всегда есть возможность использовать Selenium напрямую, обратившись к экземпляру WebDriver (через <code>browser.driver</code>):<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">WebElement underlyingElement <span style="color: #66cc66;color: #CCC;">=</span> $<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">'#myElement'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">getElement</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #cc66cc;color: #DDD;">0</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
<span style="color: #aaaadd; font-weight: bold;color: #8FB394;">Action</span> action <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> Actions<span style="color: #66cc66;color: #CCC;">&#40;</span>browser.<span style="color: #006600;">driver</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    .<span style="color: #006600;">clickAndHold</span><span style="color: #66cc66;color: #CCC;">&#40;</span>underlyingElement<span style="color: #66cc66;color: #CCC;">&#41;</span>
    .<span style="color: #006600;">moveByOffset</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #cc66cc;color: #DDD;">15</span>,<span style="color: #cc66cc;color: #DDD;">15</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    .<span style="color: #006600;">release</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    .<span style="color: #006600;">build</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
action.<span style="color: #006600;">perform</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span></pre></div></div>

<p>Класс <strong><a href="http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/interactions/Actions.html">Actions</a></strong> &#8211; это низкоуровневый способ создать последовательность браузерных действий с тем, чтобы их последовательно выполнить методом <code>perform()</code>. Обращаю внимание, что <code>Actions</code> по своему функционалу <strong>мощнее</strong>, чем просто вызовы JavaScript-кода и далеко не все действия пользователя можно имитировать через JavaScript. Реализация <code>Actions</code> на стороне браузера сильно зависит от того, какой драйвер используется и не является переносимой. Использование <code>Actions</code> &#8211; практически единственный вариант для генерации tap-событий в планшетах с touch screen. Есть и ограничения &#8211; <code>Actions</code> не позволяет вам совершать манипуляции с Flash-компонентами на странице; это возможно только через <code>ExternalInterface</code> при помощи JavaScript, что сильно сложнее.<br />
<br/></p>
<h2>Geb внутри Grails</h2>
<p>Для использования Geb в Grails удобно применять <a href="http://grails.org/plugin/spock">плагин Spock</a>. Я не буду долго распространяться про Spock Framework, информации о нем достаточно. Spock удобнее стандартного JUnit прежде всего тем, что позволяет писать тесты на своем мета-языке <em>спецификаций</em> (опять-таки на базе Groovy). Это получается и короче, и выразительнее.</p>
<p>Несколько слов о настройке Geb для Grails 1.3.x. Пример такого проекта выложен <a href="https://github.com/geb/geb-example-grails/">здесь</a>. Нужная секция в <strong>BuildConfig.groovy</strong> будет выглядеть так:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">dependencies <span style="color: #66cc66;color: #CCC;">&#123;</span>
    test<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;org.seleniumhq.selenium:selenium-htmlunitdriver:$seleniumVersion&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        exclude <span style="color: #ff0000;color: #666666;">&quot;xml-apis&quot;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
    test<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    test<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
&nbsp;
    test <span style="color: #ff0000;color: #666666;">&quot;org.codehaus.geb:geb-spock:$gebVersion&quot;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span>
plugins <span style="color: #66cc66;color: #CCC;">&#123;</span>
    test <span style="color: #ff0000;color: #666666;">&quot;:tomcat:$grailsVersion&quot;</span>
    test <span style="color: #ff0000;color: #666666;">&quot;:hibernate:$grailsVersion&quot;</span>
&nbsp;
    test <span style="color: #ff0000;color: #666666;">&quot;:geb:$gebVersion&quot;</span>
    test <span style="color: #ff0000;color: #666666;">&quot;:spock:0.5-groovy-1.7&quot;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Текущий gebVersion &#8211; 0.6.2, подставьте нужный.</p>
<p>Мы знаем, что в Grails есть два типа тестов &#8211; test/unit и test/integration. Spock добавляет еще и test/functional &#8211; &#8220;функциональные&#8221; тесты.</p>
<p>Geb внутри функциональных тестов (спецификаций Spock) похож на обыкновенный, но запускать браузер и конфигурировать драйвер внутри теста не нужно &#8211; все уже сделано. Пример простенькой спецификации (предполагаем, что классы <code>HomePage</code>, <code>LoginPage</code> нами уже описаны):<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">@Stepwise
<span style="color: #000000; font-weight: bold;color: #577A61;">class</span> CoreSpec <span style="color: #000000; font-weight: bold;color: #577A61;">extends</span> GebReportingSpec <span style="color: #66cc66;color: #CCC;">&#123;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;color: #577A61;">def</span> <span style="color: #ff0000;color: #666666;">&quot;unauthorized user goes to login page&quot;</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        when:
        to HomePage
        then:
        at LoginPage
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Каждый метод внутри спецификации представляет собой отдельный тест. Сначала этот тест пытается зайти на главную страницу, но поскольку пользователь не авторизован, в итоге он должен попасть на <code>LoginPage</code>.</p>
<p>Конечно, есть еще файлы настройки Geb, которые определяют:</p>
<ul>
<li>Какой браузер использовать и с каким драйвером</li>
<li>Разнообразные настройки Geb (такие как таймаут)</li>
</ul>
<p>В демонстрационном примере есть пример файла <a href="https://github.com/geb/geb-example-grails/blob/master/test/functional/GebConfig.groovy">GebConfig.groovy</a>.</p>
<p>Вроде бы этого достаточно. Набираем:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="sh" style="font-family:monospace;color: #5C5F4A;">grails -Dgeb.env=firefox test-app :spock</pre></div></div>

<p>и смотрим, как наш робот пытается что-то там тестировать. </p>
<p>Быстро выясняется, что на Chrome и IE наши тесты не работают. Причина в том, что реально с нуля и без подручных средств можно запустить либо HtmlUnit (который для тестирования бесполезен) либо Firefox. Для Firefox автоматически создается новый профиль, который &#8220;расширяет&#8221; браузер для последующего управления через FirefoxDriver. Да, Selenium/Geb замечательно работает на Firefox, потому что именно для Firefox он изначально и разрабатывался. </p>
<p>Что касается IE и Chrome, то с ними все несколько сложнее. Общую ситуацию можно понять из таблички:</p>
<table border="1" cellspacing="0" cellpadding="5" style="padding: 5px;">
<tr>
<th width="20%">Браузер</th>
<th>Что потребуется</th>
<th>Ограничения</th>
</tr>
<tr>
<td><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/logo-firefox.png" width="32" height="32" alt="firefox">&nbsp;Firefox</td>
<td>&mdash;</td>
<td>Под некоторыми OS подвержен <a href="http://code.google.com/p/selenium/wiki/ScalingWebDriver">проблеме исчерпания TCP-портов</a>.</td>
</tr>
<tr>
<td><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/logo-chrome.png" alt="Chrome" title="logo-chrome" width="32" height="32">&nbsp;Chrome</td>
<td>Потребуется скачать и установить отдельный платформо-зависимый сервис (ChromeDriverService).</td>
<td>Отсутствует поддержка <code>Actions</code>.</td>
</tr>
<td><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/logo-opera.png" width="32" height="32" />&nbsp;Opera</td>
<td>&mdash;</td>
<td>Не ниже версии 11.5, только один экземпляр браузера единовременно.</td>
</tr>
<tr>
<td><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/logo-ie.png" width="32" height="32" />&nbsp;Internet&nbsp;Explorer</td>
<td>Требует запуска отдельного сервиса. Требует предварительной настройки браузера (Protection Mode).</td>
<td>Версии 6,7,8,9. Поддерживает ровно один экземпляр браузера.</td>
</tr>
</table>
<p>Как видим, драйвер Chrome не поддерживает <code>Actions</code>! Что довольно неприятно.</p>
<p>Что еще официально поддерживает Selenium (а, следовательно, Geb)?</p>
<ul>
<li>Браузер iPhone</li>
<li>Браузер Android</li>
</ul>
<h2>Писать ли браузерные тесты?</h2>
<p>Прелесть браузерных тестов в гарантированности поведения на конкретном браузере.</p>
<p>Однако, браузерные тесты:</p>
<ul>
<li>Работают медленно (гораздо медленнее чем любой unit-тест) &#8211; просто в силу того, что скорость реакции браузера ограничена.</li>
<li>При тестировании сложной JavaScript/AJAX-начинки &#8211; трудны в написании</li>
<li>Требуют поддержки. Любое изменение CSS-классов и иерархии страницы может сломать тесты. Причем, как правило, число таких вот мелких визуальных изменений сильно превышает число изменений, скажем, в схеме БД или бизнес-логике.</li>
</ul>
<p>Простейшая формула, описывающая необходимость написания автоматических тестов, может выглядеть так:</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2012/02/geb-auto.png" alt="Нужно ли писать автоматические тесты?" title="Нужно ли писать автоматические тесты?" width="730" height="410" class="alignnone size-full wp-image-992" /></p>
<p>Формула красивая, но довольно бессмысленная, так как заранее оценить трудоемкость поддержки и стоимость ручного тестирования итерации очень затруднительно. Но ясно, что с ростом числа итераций мысль автоматизировать тестирование будет приходить вам в голову все чаще и чаще. На низкую скорость выполнения браузерных тестов можно не обращать много внимания &#8211; это все равно быстрее, чем выполнять ручное тестирование. Правда, формула не учитывает того, что не вся ручная работа может быть автоматизирована &#8211; попробуйте, скажем, объяснить роботу, что значит &#8220;верстка поехала&#8221;.</p>
<p>Есть также свои тонкости в браузерном тестировании. Например, Selenium не всегда способен самостоятельно определить, доступен ли вообще веб-сайт, на который зашел браузер. Действительно, в случае, скажем, вырубания Интернета браузер вроде бы <em>что-то</em> показывает. Но это не ваша ожидаемая страница, а внутреннее сообщение браузера в стиле &#8220;проверьте свои настройки Интернет&#8221;. Все эти ситуации вам придется самим определять и включать в тестовый пакет. Помимо этого, иногда приходится приспосабливать приложение под автоматические тесты, идя на какие-то компромиссы.</p>
<p>Вообще, это может звучать непривычно, но автоматический тест &#8211; это программа. Следовательно, иногда её разработку и поддержку можно поручить программистам. Этот подход нивелирует строгое разделение труда между программистом и тестировщиком. В рамках налаженной системы сборки и тестирования часть применяют принцип &#8220;сам сломал тест, сам и чини&#8221;. То есть, грубо говоря, неважно, кто тест написал &#8211; с момента создания он становится общей собственностью и ответственность за работу теста несут как тестировщики, так и программисты.</p>
<p>Отдельно стоит упомянуть про так называемый recording &#8211; возможность записывать ваши действия в браузере и на их основе создавать тесты. Эту возможность предоставляет Selenium IDE в Firefox. Звучит это здорово, однако созданные таким образом тесты обычно обладают низкой <em>устойчивостью</em>, то есть вероятность их поломки при какой-то изменения страницы весьма велика. Происходит это потому, что recording не знает, как правильно адресовать блоки страницы, с которыми вы взаимодействуете &#8211; нужно ли использовать ли CSS-классы, #id или какие-то иные способы. Кроме того, логику (начинку) тестов Selenium за вас все равно придумать не сможет.</p>
<p>Резюмирую: Geb &#8211; приятный в использовании и вполне работающий продукт (не особо обращаем внимание на номер версии, ибо стабильность работы Geb обеспечивает Selenium), вполне годный к написанию браузерных тестов для Grails-приложений. Оставляю вас с ним наедине: <a href="http://gebish.org">http://gebish.org</a>.</p>
<p/>
<p/>
<br/></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2012/03/01/in-browser-testing-with-geb/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Оценка эффективности filesystem кеша</title>
		<link>http://blog.gramant.ru/2011/08/10/filesystem_cache_hit_ratio/</link>
		<comments>http://blog.gramant.ru/2011/08/10/filesystem_cache_hit_ratio/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 14:24:15 +0000</pubDate>
		<dc:creator>Денис</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Filesystem]]></category>
		<category><![CDATA[hdd]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[IO]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[SystemTap]]></category>
		<category><![CDATA[диски]]></category>
		<category><![CDATA[производительность]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=914</guid>
		<description><![CDATA[На нашем видео-пректе при отдаче видео с серверов возникает вопрос производительност дисковой подсистемы. Очевидно, что гигабитные сетевые интерфейсы обладают большей производительностью, чем, скажем, RAID0 из 2х дисков. И если бы видео-ролики обладали одинаковой популярностью, то диски являлись бы узким местом при отдаче контента. Однако же, нам повезло и всегда есть небольшой набор роликов, которые делают [...]]]></description>
			<content:encoded><![CDATA[<p>На нашем видео-пректе при отдаче видео с серверов возникает вопрос производительност дисковой подсистемы. Очевидно, что гигабитные сетевые интерфейсы обладают большей производительностью, чем, скажем, RAID0 из 2х дисков. И если бы видео-ролики обладали одинаковой популярностью, то диски являлись бы узким местом при отдаче контента.<br />
Однако же, нам повезло и всегда есть небольшой набор роликов, которые делают 80% трафика, и длинный хвост с редкой посещаемостью. Этот небольшой набор оседает в кеше файловой системы и отдается практически без дисковой активности.<br />
Например, если у нас на сервере лежит 100 гигабайт роликов, а памяти на сервере 24 гигабайта, то мы можен нарисовать такой график. По горизонтали у нас будут &#8220;файлы&#8221;, отсортированные по популярности, по вертикали &#8212; трафик, порождаемый этими файлами. Суммарный трафик &#8212; площадь поверхности под графиком.</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2011/08/filesystemcache.png" alt="filesystemcache" title="filesystemcache" width="800" height="569" class="alignnone size-full wp-image-915" /></p>
<p>С другой стороны, всегда стоит вопрос, сколько памяти на сервер лучше поставить и как померять hit/miss ratio. В этом посте мы рассмотрим, как это можно сделать.<br />
<span id="more-914"></span><br />
Насколько мне известно, нет стандартной утилиты, которая бы собирала cache hit ratio для кеша файловой системы, но мы собираем это при помощи <a href="http://sourceware.org/systemtap/">SystemTap</a>.<br />
Вот скрипт, комментарии ниже.</p>
<pre>
global total_bytes, disk_bytes, counter, overall_cache_bytes, overall_disk_bytes

probe vfs.read.return {
        if ($return > 0) {
                total_bytes += $return
        }
}

probe generic.fop.sendfile.return {
	if ($return > 0) {
		total_bytes += $return
	}
}

probe ioblock.request {
        if (rw == 0 &#038;&#038; size > 0 &#038;&#038; devname == "dm-0") {
                disk_bytes += size
        }
}

probe begin {
	disk_bytes = 0
	total_bytes = 0
}

probe timer.s(1) {
        if (counter%15 == 0) {
                printf ("\n%18s %18s %18s %10s %10s\n",
                        "Total Reads (KB)", "Cache Reads (KB)", "Disk Reads (KB)", "Miss Rate", "Hit Rate")
        }
        counter++

        cache_bytes = total_bytes - disk_bytes
        if (cache_bytes < 0) {
                cache_bytes = 0
	}
        if (cache_bytes+disk_bytes > 0) {
                hitrate =  10000 * cache_bytes / (cache_bytes+disk_bytes)
                missrate = 10000 * disk_bytes / (cache_bytes+disk_bytes)
        } else {
                hitrate = 0
                missrate = 0
        }
        printf ("%18d %18d %18d %6d.%02d%% %6d.%02d%%\n", total_bytes/1024, cache_bytes/1024, disk_bytes/1024, missrate/100, missrate%100, hitrate/100, hitrate%100)
        overall_cache_bytes += cache_bytes
        overall_disk_bytes += disk_bytes
        total_bytes = 0
        disk_bytes = 0
}

probe end {
        avg_hitrate =  10000 * overall_cache_bytes / ( overall_cache_bytes + overall_disk_bytes )
        avg_missrate = 10000 * overall_disk_bytes  / ( overall_cache_bytes + overall_disk_bytes )
        printf("\n%s: %d.%02d\n%s: %d.%02d\n",
                " Average Hit Rate", avg_hitrate/100, avg_hitrate%100,
                "Average Miss Rate", avg_missrate/100, avg_missrate%100)
}
</pre>
<p>Сервер отдает файлы не при помощи read/write, а используя sendfile. Поэтому, основной дисковый трафик накручивается в probe generic.fop.sendfile.return. Также, т.к. мы считаем только read и sendfile, а дисковый трафик может порождаться операциями записи или разными stat-ами, readdir-ами, getdents-ами и прочим, total_bytes могут оказаться меньше, чем read_bytes. Но у нас на сервере 99% процентов операций &#8212; это read или sendfile, поэтому мы на такое закрываем глаза, вносимые искажения довольно малы.</p>
<p>Результат получается примерно следующий:</p>
<pre>
# stap cache-hit-rate.stp
  Total Reads (KB)   Cache Reads (KB)    Disk Reads (KB)  Miss Rate   Hit Rate
            116150             113894               2256      1.94%     98.05%
            119515             116643               2872      2.40%     97.59%
            117422             114438               2984      2.54%     97.45%
            115679             112203               3476      3.00%     96.99%
            121254             118526               2728      2.24%     97.75%
            119375             116059               3316      2.77%     97.22%
            114101             109885               4216      3.69%     96.30%
            114776             112416               2360      2.05%     97.94%
            118970             116062               2908      2.44%     97.55%
            121416             117820               3596      2.96%     97.03%
            115386             112790               2596      2.24%     97.75%
            119955             116831               3124      2.60%     97.39%
            118245             115893               2352      1.98%     98.01%
            118250             115398               2852      2.41%     97.58%
            117079             114171               2908      2.48%     97.51%

  Total Reads (KB)   Cache Reads (KB)    Disk Reads (KB)  Miss Rate   Hit Rate
            112801             110241               2560      2.26%     97.73%
            118078             115742               2336      1.97%     98.02%
            117863             114659               3204      2.71%     97.28%
            119600             117120               2480      2.07%     97.92%
            118293             114977               3316      2.80%     97.19%

Average Hit Rate: 97.51
Average Miss Rate: 2.48
#
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/08/10/filesystem_cache_hit_ratio/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Пишем deploy-скрипт для Grails</title>
		<link>http://blog.gramant.ru/2011/07/13/grails-deploy-script/</link>
		<comments>http://blog.gramant.ru/2011/07/13/grails-deploy-script/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 11:28:55 +0000</pubDate>
		<dc:creator>Сергей Нековаль</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[deploy]]></category>
		<category><![CDATA[gant]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jsch]]></category>
		<category><![CDATA[scp]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[tomcat]]></category>
		<category><![CDATA[war]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=879</guid>
		<description><![CDATA[Зачем нужен deploy-скрипт Grails-приложения очень легко собираются в WAR. Делается это так: grails war Помимо того, что WAR собирается, очень хочется этот WAR еще и установить на сервер. В нашем случае это Tomcat. Установка вручную требует некоторой возни: Остановить сервер. Убить процесс, если он не остановился сам. Удалить старые файлы приложения (на всякий случай) Скопировать [...]]]></description>
			<content:encoded><![CDATA[<h3>Зачем нужен deploy-скрипт</h3>
<p>Grails-приложения очень легко собираются в WAR. Делается это так:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="shell" style="font-family:monospace;color: #5C5F4A;">grails war</pre></div></div>

<p>Помимо того, что WAR собирается, очень хочется этот WAR еще и установить на сервер. В нашем случае это Tomcat. Установка вручную требует некоторой возни:</p>
<ol>
<li>Остановить сервер. Убить процесс, если он не остановился сам.</li>
<li>Удалить старые файлы приложения (на всякий случай)</li>
<li>Скопировать новый WAR на сервер. Иногда его нужно переименовывать (скажем, в <code>ROOT.war</code>)</li>
</ol>
<p>В Maven эту работу может проделать, например, cargo plugin. Но с ним много приключений и настройки, причем он не особо учитывает особенности сервере.</p>
<p>Мы также можем использовать shell-скрипт. Но зачем писать на неудобном языке shell, когда есть замечательный кроссплатформенный язык Groovy?</p>
<p><span id="more-879"></span></p>
<h3>Пишем скрипт на Groovy</h3>
<p>Grails позволяет легко создавать на Groovy command-line скрипты, которые складываются в папку scripts нашего приложения. Любой из этих скриптов затем можно запустить консольной командой <code>grails</code>. Мы будем писать скрипт под названием <strong>Deploy.groovy</strong>.</p>
<p>Приятно, что внутри скрипта можно использовать конфигурационные данные нашего Grails-приложения. Например, имя сервера и имя пользователя являются environment-specific. Для Grails 1.3.7 мы можем получить доступ к конфигурации так:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">depends<span style="color: #66cc66;color: #CCC;">&#40;</span>compile<span style="color: #66cc66;color: #CCC;">&#41;</span>
depends<span style="color: #66cc66;color: #CCC;">&#40;</span>createConfig<span style="color: #66cc66;color: #CCC;">&#41;</span>          <span style="color: #808080; font-style: italic;color: #CDC;">/* Магическая конструкция Gant, которая загружает Config.groovy */</span>
<span style="color: #000000; font-weight: bold;color: #577A61;">def</span> host <span style="color: #66cc66;color: #CCC;">=</span> ConfigurationHolder.<span style="color: #006600;">config</span><span style="color: #66cc66;color: #CCC;">?</span>.<span style="color: #006600;">deploy</span>.<span style="color: #006600;">host</span>
<span style="color: #000000; font-weight: bold;color: #577A61;">def</span> username <span style="color: #66cc66;color: #CCC;">=</span> ConfigurationHolder.<span style="color: #006600;">config</span><span style="color: #66cc66;color: #CCC;">?</span>.<span style="color: #006600;">deploy</span>.<span style="color: #006600;">username</span></pre></div></div>

<p>Предполагается, что где внутри <strong>Config.deploy</strong> будут такие строчки:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">environments <span style="color: #66cc66;color: #CCC;">&#123;</span>
    production <span style="color: #66cc66;color: #CCC;">&#123;</span>
        deploy <span style="color: #66cc66;color: #CCC;">&#123;</span>
            host <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #ff0000;color: #666666;">'www1.shards.intra'</span>
            username <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #ff0000;color: #666666;">'deployer'</span>
        <span style="color: #66cc66;color: #CCC;">&#125;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Небольшая хитрость состоит в том, что для загрузки конфигурации сначала нужно скомпилировать файл <strong>Config.groovy</strong>. Для этого мы объявили <strong>depends(compile)</strong>, где <strong>compile</strong> &#8211; уже известная Gant команда (задача) компиляции проекта.</p>
<h3>Используем SSH</h3>
<p>Нам нужно проделывать много всяких операций на сервере с SSH-доступом. Для простоты я взял бесплатную библиотеку JSch и ограничился вариантом доступа с паролем. Поэтому наш скрипт начинается так:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">@GrabResolver<span style="color: #66cc66;color: #CCC;">&#40;</span>name<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'jcraft'</span>, root<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'http://jsch.sourceforge.net/maven2'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
@Grab<span style="color: #66cc66;color: #CCC;">&#40;</span>group<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'com.jcraft'</span>, module<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'jsch'</span>, version<span style="color: #66cc66;color: #CCC;">=</span><span style="color: #ff0000;color: #666666;">'0.1.44'</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
<span style="color: #000000; font-weight: bold;color: #577A61;">import</span> com.<span style="color: #006600;">jcraft</span>.<span style="color: #006600;">jsch</span>.<span style="color: #66cc66;color: #CCC;">*</span></pre></div></div>

<p>Далее проделаем некие магические манипуляции с JSch. Нам нужно две вещи:</p>
<ul>
<li>Запускать Unix-команды на сервере</li>
<li>Копировать файлы на сервер</li>
</ul>
<p>У нас есть в распоряжении язык Groovy, поэтому попытаемся сделать мини-DSL с нужными нам функциями. Добавим пару новых методов в объект Session (из JSch), который представляет собой SSH-сессию. Для начала метод <strong>exec</strong> для выполнения команды на сервере:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Session.<span style="color: #006600;">metaClass</span>.<span style="color: #006600;">exec</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">String</span> cmd <span style="color: #66cc66;color: #CCC;">-&gt;</span>
    Channel channel <span style="color: #66cc66;color: #CCC;">=</span> openChannel<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;exec&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    channel.<span style="color: #006600;">command</span> <span style="color: #66cc66;color: #CCC;">=</span> cmd
    channel.<span style="color: #006600;">inputStream</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">null</span>
    channel.<span style="color: #006600;">errStream</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">System</span>.<span style="color: #006600;">err</span>
    <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">InputStream</span> inp <span style="color: #66cc66;color: #CCC;">=</span> channel.<span style="color: #006600;">inputStream</span>
    channel.<span style="color: #006600;">connect</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #993333;color: #343832;">int</span> exitStatus <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">-</span><span style="color: #cc66cc;color: #DDD;">1</span>
    StringBuilder output <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> StringBuilder<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">try</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        <span style="color: #b1b100;color: #B83A24;">while</span> <span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #000000; font-weight: bold;color: #577A61;">true</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
            output <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> inp
            <span style="color: #b1b100;color: #B83A24;">if</span> <span style="color: #66cc66;color: #CCC;">&#40;</span>channel.<span style="color: #006600;">closed</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                exitStatus <span style="color: #66cc66;color: #CCC;">=</span> channel.<span style="color: #006600;">exitStatus</span>
                <span style="color: #000000; font-weight: bold;color: #577A61;">break</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span>
            <span style="color: #000000; font-weight: bold;color: #577A61;">try</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                sleep<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #cc66cc;color: #DDD;">1000</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span> <span style="color: #000000; font-weight: bold;color: #577A61;">catch</span> <span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #aaaadd; font-weight: bold;color: #8FB394;">Exception</span> ee<span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span>
        <span style="color: #66cc66;color: #CCC;">&#125;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span> <span style="color: #000000; font-weight: bold;color: #577A61;">finally</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        channel.<span style="color: #006600;">disconnect</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;color: #B83A24;">if</span> <span style="color: #66cc66;color: #CCC;">&#40;</span>exitStatus <span style="color: #66cc66;color: #CCC;">!=</span> <span style="color: #cc66cc;color: #DDD;">0</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        <span style="color: #993399;">println</span> output
        <span style="color: #000000; font-weight: bold;color: #577A61;">throw</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">RuntimeException</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;Command [${cmd}] returned exit-status ${exitStatus}&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
    output.<span style="color: #006600;">toString</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Из соображений краткости я сделал так, что при успешном выполнении метод не выводит ничего, а при возникновении ошибок печатает весь выходной поток выполненной команды.</p>
<p>Теперь бы еще записать файл на сервер:<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Session.<span style="color: #006600;">metaClass</span>.<span style="color: #006600;">scp</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span> sourceFile, dst <span style="color: #66cc66;color: #CCC;">-&gt;</span>
    ChannelSftp channel <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#40;</span>ChannelSftp<span style="color: #66cc66;color: #CCC;">&#41;</span> openChannel<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;sftp&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    channel.<span style="color: #006600;">connect</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #993399;">println</span> <span style="color: #ff0000;color: #666666;">&quot;${sourceFile.path} =&gt; ${dst}&quot;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">try</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        channel.<span style="color: #006600;">put</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #000000; font-weight: bold;color: #577A61;">new</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">FileInputStream</span><span style="color: #66cc66;color: #CCC;">&#40;</span>sourceFile<span style="color: #66cc66;color: #CCC;">&#41;</span>, dst, <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> SftpProgressMonitor<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
&nbsp;
            <span style="color: #000000; font-weight: bold;color: #577A61;">private</span> <span style="color: #993333;color: #343832;">int</span> <span style="color: #663399;">max</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #cc66cc;color: #DDD;">1</span>
            <span style="color: #000000; font-weight: bold;color: #577A61;">private</span> <span style="color: #993333;color: #343832;">int</span> points <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #cc66cc;color: #DDD;">0</span>
            <span style="color: #000000; font-weight: bold;color: #577A61;">private</span> <span style="color: #993333;color: #343832;">int</span> current <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #cc66cc;color: #DDD;">0</span>
&nbsp;
            <span style="color: #993333;color: #343832;">void</span> init<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #993333;color: #343832;">int</span> op, <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">String</span> src, <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">String</span> dest, <span style="color: #993333;color: #343832;">long</span> <span style="color: #663399;">max</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                <span style="color: #000000; font-weight: bold;color: #577A61;">this</span>.<span style="color: #663399;">max</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #663399;">max</span>
                <span style="color: #000000; font-weight: bold;color: #577A61;">this</span>.<span style="color: #006600;">current</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #cc66cc;color: #DDD;">0</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
            <span style="color: #993333;color: #343832;">boolean</span> <span style="color: #663399;">count</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #993333;color: #343832;">long</span> <span style="color: #663399;">count</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                current <span style="color: #66cc66;color: #CCC;">+=</span> <span style="color: #663399;">count</span>
                <span style="color: #993333;color: #343832;">int</span> newPoints <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#40;</span>current <span style="color: #66cc66;color: #CCC;">*</span> <span style="color: #cc66cc;color: #DDD;">20</span> / <span style="color: #663399;">max</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #000000; font-weight: bold;color: #577A61;">as</span> <span style="color: #993333;color: #343832;">int</span>
                <span style="color: #b1b100;color: #B83A24;">if</span> <span style="color: #66cc66;color: #CCC;">&#40;</span>newPoints <span style="color: #66cc66;color: #CCC;">&gt;</span> points<span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                    <span style="color: #993399;">print</span> <span style="color: #ff0000;color: #666666;">'.'</span>
                <span style="color: #66cc66;color: #CCC;">&#125;</span>
                points <span style="color: #66cc66;color: #CCC;">=</span> newPoints
                <span style="color: #000000; font-weight: bold;color: #577A61;">true</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
            <span style="color: #993333;color: #343832;">void</span> end<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
                <span style="color: #993399;">println</span> <span style="color: #ff0000;color: #666666;">''</span>
            <span style="color: #66cc66;color: #CCC;">&#125;</span>
&nbsp;
        <span style="color: #66cc66;color: #CCC;">&#125;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span> <span style="color: #000000; font-weight: bold;color: #577A61;">finally</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        channel.<span style="color: #006600;">disconnect</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Собственно, вся основная начинка этого метода &#8211; индикатор прогресса.</p>
<p>Ну и наконец, для изготовления полноценного DSL нам нужен closure, к которому мы прицепим наши конструкции. Это делается, например, так (прицепляем к методу doRemote):<br />
<br/></p>

<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">Session.<span style="color: #006600;">metaClass</span>.<span style="color: #006600;">doRemote</span> <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #66cc66;color: #CCC;">&#123;</span> Closure closure <span style="color: #66cc66;color: #CCC;">-&gt;</span>
    connect<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #000000; font-weight: bold;color: #577A61;">try</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        closure.<span style="color: #006600;">delegate</span> <span style="color: #66cc66;color: #CCC;">=</span> delegate
        closure.<span style="color: #993399; font-weight: bold;">call</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span> <span style="color: #000000; font-weight: bold;color: #577A61;">finally</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        disconnect<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Метод <strong>doRemote</strong> образует &#8220;скобки&#8221;, внутри которых мы сможем употреблять методы <strong>exec</strong> и <strong>scp</strong>.</p>
<h3>Наконец, записываем саму процедуру deploy</h3>
<p>Собственно, тело нашего скрипта будет выглядеть примерно так:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #808080; font-style: italic;color: #CDC;">// Описание нашего скрипта. Оно появится в grails help.</span>
target<span style="color: #66cc66;color: #CCC;">&#40;</span>main: <span style="color: #ff0000;color: #666666;">&quot;Выложить WAR-файл на сервер.&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
    .. Инициализируем JSch
    JSch jsch <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> JSch<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">Properties</span> config <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">Properties</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    config.<span style="color: #006600;">put</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;StrictHostKeyChecking&quot;</span>, <span style="color: #ff0000;color: #666666;">&quot;no&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    config.<span style="color: #006600;">put</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;HashKnownHosts&quot;</span>,  <span style="color: #ff0000;color: #666666;">&quot;yes&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    jsch.<span style="color: #006600;">config</span> <span style="color: #66cc66;color: #CCC;">=</span> config
&nbsp;
    <span style="color: #808080; font-style: italic;color: #CDC;">// Как уже описано выше, загружаем host и username из конфигурации.</span>
    ...
&nbsp;
    <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">String</span> password <span style="color: #66cc66;color: #CCC;">=</span> <span style="color: #000000; font-weight: bold;color: #577A61;">new</span> <span style="color: #aaaadd; font-weight: bold;color: #8FB394;">String</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #aaaadd; font-weight: bold;color: #8FB394;">System</span>.<span style="color: #006600;">console</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>.<span style="color: #006600;">readPassword</span><span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;Enter password for ${username}@${host}: &quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    Session session <span style="color: #66cc66;color: #CCC;">=</span> jsch.<span style="color: #006600;">getSession</span><span style="color: #66cc66;color: #CCC;">&#40;</span>username, host, <span style="color: #cc66cc;color: #DDD;">22</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
    session.<span style="color: #006600;">setPassword</span><span style="color: #66cc66;color: #CCC;">&#40;</span>password<span style="color: #66cc66;color: #CCC;">&#41;</span>
    session.<span style="color: #006600;">doRemote</span> <span style="color: #66cc66;color: #CCC;">&#123;</span>
        exec <span style="color: #ff0000;color: #666666;">&quot;что-то там&quot;</span>
        ...
        <span style="color: #006600;">scp</span> warFile, <span style="color: #ff0000;color: #666666;">'/opt/tomcat/latest/webapps/ROOT.war'</span>
        ...
    <span style="color: #66cc66;color: #CCC;">&#125;</span>
<span style="color: #66cc66;color: #CCC;">&#125;</span></pre></div></div>

<p>Теперь, собственно, надо понять, где лежит WAR-файл и как сообщить системе, что перед процедурой развертывания хорошо бы его собрать.</p>
<p>Это делается уже известной нам командой Gant под названием <strong>depends</strong>:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">depends<span style="color: #66cc66;color: #CCC;">&#40;</span>clean<span style="color: #66cc66;color: #CCC;">&#41;</span>
depends<span style="color: #66cc66;color: #CCC;">&#40;</span>war<span style="color: #66cc66;color: #CCC;">&#41;</span></pre></div></div>

<p>Сначала чистим проект, потом собираем WAR. Что касается доступа к WAR-файлу, нет ничего невозможного. Всем скриптам доступна переменная <strong>grailsSettings</strong>, у которой в том числе можно узнать и где он лежит:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;"><span style="color: #aaaadd; font-weight: bold;color: #8FB394;">File</span> warFile <span style="color: #66cc66;color: #CCC;">=</span> grailsSettings.<span style="color: #006600;">projectWarFile</span></pre></div></div>

<p>Подробнее про <strong>grailsSettings</strong> написано в документации по Grails.</p>
<p>Собственно, все готово, остался последний штрих. У нас в скрипте заявлена только одна задача (main), назначим ее для запуска по умолчанию:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">setDefaultTarget<span style="color: #66cc66;color: #CCC;">&#40;</span>main<span style="color: #66cc66;color: #CCC;">&#41;</span></pre></div></div>

<p>Помимо этого, мы используем встроенные скрипты Grails, такие как: <strong>compile</strong>, <strong>war</strong> и т.п. Для их импорта внутрь нашего скрипта (чтобы на них можно было ссылаться командой <strong>depends</strong>) добавим в начало скрипта следующее:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="groovy" style="font-family:monospace;color: #5C5F4A;">includeTargets <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> grailsScript<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;Clean&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
includeTargets <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> grailsScript<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;Init&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span>
includeTargets <span style="color: #66cc66;color: #CCC;">&lt;&lt;</span> grailsScript<span style="color: #66cc66;color: #CCC;">&#40;</span><span style="color: #ff0000;color: #666666;">&quot;War&quot;</span><span style="color: #66cc66;color: #CCC;">&#41;</span></pre></div></div>

<p>Для экономии места я не публикую итоговый скрипт целиком. Посмотреть готовый скрипт для Tomcat можно <a href="https://github.com/spn/habr/blob/master/Deploy.groovy">здесь</a>.</p>
<h3>Заключение</h3>
<p>Мы собрали небольшой мини-фреймворк для написания deploy-скриптов с возможность доступа к серверам по SSH. Запустить его мы можем так:</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="shell" style="font-family:monospace;color: #5C5F4A;">grails deploy</pre></div></div>

<p>Запустив</p>
<p>
<div class="wp_syntax"><div class="code"><pre class="shell" style="font-family:monospace;color: #5C5F4A;">grails help deploy</pre></div></div>

<p>мы даже сможем получить инструкции по использованию скрипта <img src='http://blog.gramant.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>По сравнению с shell-скриптами это дает нам следующие преимущества:</p>
<ul>
<li>Существенно более мощный язык для написания скрипта.</li>
<li>Скрипт интегрирован в Grails-проект и имеет доступ к конфигурации. Это позволяет делать развертывание по-разному в зависимости от текущего Grails environment, например: <strong>grails prod deploy</strong>.</li>
<li>Получаем доступ к любым средствам Gant (и, соответственно, Ant).</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/07/13/grails-deploy-script/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Способ защиты медиа-контента. Возможен ли DRM в Вебе.</title>
		<link>http://blog.gramant.ru/2011/05/11/%d1%81%d0%bf%d0%be%d1%81%d0%be%d0%b1-%d0%b7%d0%b0%d1%89%d0%b8%d1%82%d1%8b-%d0%bc%d0%b5%d0%b4%d0%b8%d0%b0-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%bd%d1%82%d0%b0-%d0%b2%d0%be%d0%b7%d0%bc%d0%be%d0%b6%d0%b5/</link>
		<comments>http://blog.gramant.ru/2011/05/11/%d1%81%d0%bf%d0%be%d1%81%d0%be%d0%b1-%d0%b7%d0%b0%d1%89%d0%b8%d1%82%d1%8b-%d0%bc%d0%b5%d0%b4%d0%b8%d0%b0-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%bd%d1%82%d0%b0-%d0%b2%d0%be%d0%b7%d0%bc%d0%be%d0%b6%d0%b5/#comments</comments>
		<pubDate>Wed, 11 May 2011 07:08:04 +0000</pubDate>
		<dc:creator>Александр</dc:creator>
				<category><![CDATA[Без категории]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=874</guid>
		<description><![CDATA[На прошлой неделе состоялась конференция «Российские интернет-технологии», на которой мы о нашем опыте работы с потоковым видео и защите видео контента. Довольно большая часть наших проектов связана с хранением и передачей видео-контента в интернете. Одним из часто задаваемых нашими заказчиками вопросов является вопрос защиты контента от нелегального копирования и распространения. Бытует миф, что существуют программные [...]]]></description>
			<content:encoded><![CDATA[<p>На прошлой неделе состоялась конференция «Российские интернет-технологии», на которой мы о нашем опыте работы с потоковым видео и защите видео контента.</p>
<p>Довольно большая часть наших проектов связана с хранением и передачей видео-контента в интернете. Одним из часто задаваемых нашими заказчиками вопросов является вопрос защиты контента от нелегального копирования и распространения. Бытует миф, что существуют программные продукты и технологии, способные гарантировать невозможность копирования и распространения видео-контента злоумышленниками. Но на самом деле эти продукты предоставляют лишь видимость этого.</p>
<p>В своем докладе мы постарались рассказать о существующих технологиях, их уязвимостях и об открытых и бесплатных аналогах, которые предоставляют тот-же уровень защиты, что и закрытые и очень дорогие продукты.</p>
<p>В частности мы показали что популярные протоколы &#8220;защищенного&#8221; потокового видео, такие как RTMPE, не дают гарантии сохранности контента от нелегального копирования. Любые существующие DRM системы принципиально не могут обеспечить 100% защиты.</p>
<p>В качестве альтернативы дорогим и зачастую ограниченным системам защиты контента был предложен набор простых приемов, позволяющих достичь такого же уровня безопасности.</p>
<p>Наши выступления можно посмотреть <a href="http://www.gramant.ru/AboutUs/Conference#">тут</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/05/11/%d1%81%d0%bf%d0%be%d1%81%d0%be%d0%b1-%d0%b7%d0%b0%d1%89%d0%b8%d1%82%d1%8b-%d0%bc%d0%b5%d0%b4%d0%b8%d0%b0-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%bd%d1%82%d0%b0-%d0%b2%d0%be%d0%b7%d0%bc%d0%be%d0%b6%d0%b5/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>О том, как мы пишем ТЗ</title>
		<link>http://blog.gramant.ru/2011/05/10/%d0%be-%d1%82%d0%be%d0%bc-%d0%ba%d0%b0%d0%ba-%d0%bc%d1%8b-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%82%d0%b7/</link>
		<comments>http://blog.gramant.ru/2011/05/10/%d0%be-%d1%82%d0%be%d0%bc-%d0%ba%d0%b0%d0%ba-%d0%bc%d1%8b-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%82%d0%b7/#comments</comments>
		<pubDate>Tue, 10 May 2011 05:06:16 +0000</pubDate>
		<dc:creator>Артем Вольфтруб</dc:creator>
				<category><![CDATA[Без категории]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=843</guid>
		<description><![CDATA[На прошлой неделе состоялась конференция «Российские интернет-технологии», на которой мы рассказывали о практике написания ТЗ, которую мы успешно применяем на наших проектах. Большинство систем, которые мы разрабатываем, связаны с вебом. Одной из отличительных особенностей веб проектов является их скоротечность. Цикл разработки большинства систем составляет от трех месяцев до полугода. Этому способствует несколько обстоятельств: Динамичная среда [...]]]></description>
			<content:encoded><![CDATA[<p><!-- 		@page { size: 8.27in 11.69in; margin: 0.79in; font-family: Tahoma, sans-serif; } 		P { margin-bottom: 0.08in } --> <!-- 		@page { size: 8.27in 11.69in; margin: 0.79in } 		P { margin-bottom: 0.08in } 		A:link { color: #000080; so-language: zxx; text-decoration: underline } --></p>
<p style="margin-bottom: 0in;"><span>На прошлой неделе состоялась конференция «</span><span style="color: #000080;"><span lang="zxx"><span style="text-decoration: underline;"><a href="http://ritconf.ru/"><span style="font-family: Tahoma, sans-serif;">Российские интернет-технологии</span></a></span></span></span><span>», на которой мы рассказывали о практике написания ТЗ, которую мы успешно применяем на наших проектах.</span></p>
<p style="margin-bottom: 0in;"><span>Большинство систем, которые мы разрабатываем, связаны с вебом. Одной из </span><span><span style="font-size: small;">отличительных особенностей веб проектов является их скоротечность. Цикл разработки большинства систем составляет от трех месяцев до полугода. Этому способствует несколько обстоятельств:</span></span></p>
<ol>
<li>Динамичная 	среда — интернет очень быстро меняется, 	постоянно появляются новые технологии, 	и то, что было популярным и модным 	несколько месяцев назад, сегодня уже 	не так актуально.</li>
<li><span>Конкурентный 	рынок — в битве за аудиторию важно 	выйти на рынок как можно раньше, опередив 	конкурентов.</span></li>
<li><span>Основной 	источник информации пользователи — 	практика показывает, что даже детальная 	проработка ТЗ, включая создание 	пользовательских групп, предварительное 	тестирование и т.п., не так полезна, как 	обратная связь от пользователей, которая 	может быть получена только после выхода 	первой версии системы. К тому же на 	полноценное исследование, как правило, 	нет времени (см. пункты 1,2)</span></li>
</ol>
<p style="margin-bottom: 0in;"><span>С другой стороны, работать совсем без ТЗ не получается, поскольку заказчики обычно заранее хотят знать стоимость разработки, понимать рамки и сроки проекта.</span></p>
<p style="margin-bottom: 0in;"><span><span>Именно поэтому все пишут большие и нудные ТЗ. На практике оказывается, что они не работают или работают плохо. Сроки срываются, заказчик остается недовольным, компания-разработчик теряет деньги и клиентов. В чем же причина?</span></span></p>
<p><!-- 		@page { size: 8.27in 11.69in; margin: 0.79in } 		P { margin-bottom: 0.08in } --></p>
<p style="margin-bottom: 0in;"><span><span>По нашем мнению, у традиционных, «бумажных», ТЗ есть две основные проблемы:</span></span></p>
<ol>
<li><span>«Слишком 	много букв». Заказчики неохотно читают 	большие документы. Конечно, отчасти 	это вопрос их заинтересованности в 	результате, но, тем не менее, такая 	проблема реальна.</span></li>
<li><span>Словесное 	описание интерфейса по-разному 	интерпретируется участниками процесса. 	Это самая большая проблема. Поскольку 	в веб системах значительную часть 	составляет пользовательский интерфейс 	(который, к слову, может быть весьма 	сложным и навороченным), основная задача 	этого документа — сформировать в 	головах заказчика и разработчиков 	одинаковое представление о том, как 	должна выглядеть система. К сожалению, 	словесное описание оставляет слишком 	большой простор для фантазии, поэтому 	результат в 99% случаев не соответствует 	представлению.</span></li>
</ol>
<p><!-- 		@page { size: 8.27in 11.69in; margin: 0.79in } 		P { margin-bottom: 0.08in } --></p>
<p style="margin-right: 3.26in; margin-bottom: 0in; font-weight: normal;" align="LEFT"><span><br />
</span></p>
<p style="margin-bottom: 0in;" align="LEFT"><img class="size-medium wp-image-844 alignright" title="001" src="http://blog.gramant.ru/wp-content/uploads/2011/05/001-300x211.jpg" alt="001" width="300" height="211" /></p>
<p style="margin-bottom: 0in; margin-top: 25px;" align="LEFT">Осознав эти проблемы, мы отказались от написания больших «бумажных» ТЗ и начали их рисовать. Конечно, полностью заменить текст картинками экранов не удается, поскольку в любой системе есть бизнес логика, которую нужно нужно описывать словами или с помощью диаграмм, но все, что относится к описанию пользовательских экранов, мы успешно заменяем на картинки.</p>
<p style="margin-bottom: 0in;" align="LEFT">
<p style="margin-bottom: 0in;" align="LEFT">
<p><!-- 		@page { size: 8.27in 11.69in; margin: 0.79in } 		P { margin-bottom: 0.08in } --></p>
<p style="margin-bottom: 0in;" align="LEFT">
<p style="margin-bottom: 0in;" align="LEFT">
<p style="margin-bottom: 0in; margin-top: 150px; text-align: left; " align="LEFT"><span><span>Например, такие:<br />
</span></span></p>
<p><!-- 		@page { size: 8.27in 11.69in; margin: 0.79in } 		P { margin-bottom: 0.08in } 		A:link { color: #000080; so-language: zxx; text-decoration: underline } --></p>
<p style="margin-bottom: 0in; font-weight: normal; text-align: center;" align="LEFT"><img class="aligncenter size-large wp-image-846" title="002" src="http://blog.gramant.ru/wp-content/uploads/2011/05/0021-1024x689.png" alt="002" width="819" height="551" /></p>
<p style="margin-bottom: 0in; font-weight: normal;" align="LEFT"><span><span>Это позволяет решить следующие проблемы:</span></span></p>
<ol>
<li>
<p style="margin-bottom: 0in; font-weight: normal;" align="LEFT"><span><span>Сокращается 	объем ТЗ. Его гораздо легче читать, 	поддерживать в актуальном состоянии 	и отслеживать изменения.</span></span></p>
</li>
<li>
<p style="margin-bottom: 0in; font-weight: normal;" align="LEFT"><span><span>У 	участников проекта формируется 	одинаковое представление о том, как 	будет выглядеть система. Какие 	функциональные элементы будут на том 	или ином экране, как они будут реализованы.</span></span></p>
</li>
</ol>
<p style="margin-bottom: 0in;" align="LEFT"><span><span>На рынке существует достаточно большое число инструментов, которые позволяют рисовать пользовательские экраны, не будучи профессиональным проектировщиком интерфейсов. В результате экспериментов, мы остановились на продукте </span></span><span style="color: #000080;"><span lang="zxx"><span style="text-decoration: underline;"><a href="http://balsamiq.com/"><span><span><strong>Balsamiq</strong></span></span></a></span></span></span><span><span><strong>, </strong></span></span><span><span><span style="font-weight: normal;">который успешно используем.</span></span></span></p>
<p style="margin-bottom: 0in;" align="LEFT"><span><span><span style="font-weight: normal;">Полную презентацию доклада можно посмотреть на </span></span></span><span style="color: #000080;"><span lang="zxx"><span style="text-decoration: underline;"><a href="http://www.slideshare.net/ArtemVolftrub/ss-7829792"><span style="font-family: Tahoma, sans-serif;"><span style="font-size: small;"><span style="font-weight: normal;">Slideshare.net</span></span></span></a></span></span></span></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/05/10/%d0%be-%d1%82%d0%be%d0%bc-%d0%ba%d0%b0%d0%ba-%d0%bc%d1%8b-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%82%d0%b7/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>В помощь многоязычным командам</title>
		<link>http://blog.gramant.ru/2011/03/10/%d0%b2-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d1%8f%d0%b7%d1%8b%d1%87%d0%bd%d1%8b%d0%bc-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d0%b0%d0%bc/</link>
		<comments>http://blog.gramant.ru/2011/03/10/%d0%b2-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d1%8f%d0%b7%d1%8b%d1%87%d0%bd%d1%8b%d0%bc-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d0%b0%d0%bc/#comments</comments>
		<pubDate>Thu, 10 Mar 2011 15:07:16 +0000</pubDate>
		<dc:creator>Денис</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[trac]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=823</guid>
		<description><![CDATA[У нас есть заказчики и партнеры в разных странах, и порой не все они могут комфортно изъясняться на английском языке. Мы в нашей работе используем trac для багтрекинга и документации. И поэтому мы написали и поддерживаем плагин для trac-а, который использует Google Translate и переводит тикеты и комментарии пользователей на язык, указанный в настройках пользователя. [...]]]></description>
			<content:encoded><![CDATA[<p>У нас есть заказчики и партнеры в разных странах, и порой не все они могут комфортно изъясняться на английском языке. Мы в нашей работе используем <a href="http://trac.edgewall.org/">trac</a> для багтрекинга и документации. И поэтому мы <a href="http://trac-hacks.org/wiki/GoogleTranslatePlugin">написали и поддерживаем плагин</a> для trac-а, который использует <a href="http://translate.google.com/">Google Translate</a> и переводит тикеты и комментарии пользователей на язык, указанный в настройках пользователя.</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2011/03/translate1.png" alt="Перевести" title="Перевести" width="430" height="216" class="alignnone size-full wp-image-825" /><br />
<img src="http://blog.gramant.ru/wp-content/uploads/2011/03/translate2.png" alt="Перевод" title="Перевод" width="430" height="216" class="alignnone size-full wp-image-826" /></p>
<p>Всем, кто сталкивается с подобными задачами, рекомендуем.</p>
<p><a href="http://trac-hacks.org/wiki/GoogleTranslatePlugin">Страница плагина.</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/03/10/%d0%b2-%d0%bf%d0%be%d0%bc%d0%be%d1%89%d1%8c-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d1%8f%d0%b7%d1%8b%d1%87%d0%bd%d1%8b%d0%bc-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d0%b0%d0%bc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Кодеки, контейнеры, потоки</title>
		<link>http://blog.gramant.ru/2011/02/16/%d0%ba%d0%be%d0%b4%d0%b5%d0%ba%d0%b8-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%b9%d0%bd%d0%b5%d1%80%d1%8b-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b8/</link>
		<comments>http://blog.gramant.ru/2011/02/16/%d0%ba%d0%be%d0%b4%d0%b5%d0%ba%d0%b8-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%b9%d0%bd%d0%b5%d1%80%d1%8b-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b8/#comments</comments>
		<pubDate>Wed, 16 Feb 2011 12:21:31 +0000</pubDate>
		<dc:creator>Денис</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[codecs]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=817</guid>
		<description><![CDATA[Продолжая нашу традицию &#8220;просто о сложном&#8221; и, учитывая то, что нам приходится много работать над проектами, связанными с видео в интернете, по многочисленным вопросам наших клиентов о кодеках и контейнерах, хочется об этом немного рассказать. Начнем с того, что медиа-информацию (видео и аудио) необходимо хранить в цифровом виде, и хорошо бы еще и в сжатом. [...]]]></description>
			<content:encoded><![CDATA[<p>Продолжая нашу традицию &#8220;просто о сложном&#8221; и, учитывая то, что нам приходится много работать над проектами, связанными с видео в интернете, по многочисленным вопросам наших клиентов о кодеках и контейнерах, хочется об этом немного рассказать.</p>
<p>Начнем с того, что медиа-информацию (видео и аудио) необходимо хранить в цифровом виде, и хорошо бы еще и в сжатом. Этим занимаются кодеки (codec, coder-decoder). Они переводят медиа-информацию в цифровой поток и обратно. </p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2011/02/codecs.png" alt="codecs" title="codecs" width="400" height="246" class="aligncenter size-full wp-image-818" /><br />
<span id="more-817"></span><br />
Надо сказать, что формально кодек &#8212; это реализация стандарта кодирования/декодирования. Т.е. программа или алгоритм. Но иногда кодеком называют стандарт кодирования. </p>
<p>Стандарты кодирования, как правило, оставляют нахождение алгоритмов для реализации этих стандартов разработчикам, и кодеки разных производителей, реализующие один и тот-же стандарт, могут сильно различаться по скорости работы и качеству при одинаковом битрейте. Понятие &#8220;качества&#8221;, конечно, весьма субъективно, и мнение о том, какой кодек качественнее у разных людей разные.</p>
<p>Кодеки бывают с потерей качества (lossy), бывают без потери качекства (lossless). Кодеки без потери качества сжимают до некоторого теоретического предела. Предел этот большой и медиа занимает много места. Например, час музыки &#8212; 400 МБайт. Тут, конечно, зависит от частоты сэмплирования, дискретизации (для аудио), размера картинки, количества кадров в секунду (для видео), но все равно, в среднем получается много. Кодеки с потерей качества могут сжать как угодно сильно, но за счет, как не трудно догадаться, потери в качестве.</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2011/02/loss.png" alt="loss" title="loss" width="600" height="256" class="aligncenter size-full wp-image-819" /></p>
<p>Примеры кодеков: <a href="http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC">H.264/MPEG-4 AVC</a>, <a href="http://en.wikipedia.org/wiki/Theora">Theora</a>, <a href="http://en.wikipedia.org/wiki/VP6">VP6</a>, <a href="http://en.wikipedia.org/wiki/MP3">MPEG-2 Audio Layer 3</a>. Первые три &#8212; видео-кодеки, последний &#8212; аудио-кодек. Перечислены, разумеется, далеко не все распространенные.</p>
<p>Цифровой поток, созданный энкодером (кодирующей частью кодека), хорошо бы как-то хранить. Также, хорошо бы хранить вместе видео-поток и аудио-поток. Ну и до кучи, хранить там мета-информацию: длительность, описание, и т.д. Этим занимаются контейнеры (media containers). Их задача составить индекс, где какие части какого потока лежат, перемешать эти части так, чтобы снизить накладные расходы при параллельном чтении видео и аудио потоков, расставить индексы соответствия кадров отступам в байтах и т.д.</p>
<p><img src="http://blog.gramant.ru/wp-content/uploads/2011/02/container.png" alt="container" title="container" width="401" height="275" class="aligncenter size-full wp-image-820" /></p>
<p>В общем, выбор кодека не зависит от контейнера, хотя, в некоторые контейнеры можно положить только определенные кодеки. И так же, некоторые кодеки налагают определенные ограничения на контейнеры. Например, кодеки, которые используют <a href="http://en.wikipedia.org/wiki/Video_compression_picture_types">b-frames</a>, надо класть в контейнеры, которые b-frames поддерживают.</p>
<p>Примеры контейнеров: <a href="http://en.wikipedia.org/wiki/Audio_Video_Interleave">AVI</a>, <a href="http://en.wikipedia.org/wiki/MPEG-4_Part_14">MPEG-4 Part 14 (mp4)</a>, <a href="http://en.wikipedia.org/wiki/Matroska">Matroska</a>, <a href="http://en.wikipedia.org/wiki/Ogg">Ogg</a>.</p>
<p>Довольно часто новые контейнеры и кодеки создавались руководствуясь <a href="http://en.wikipedia.org/wiki/Not_Invented_Here">NIH-принципом</a>, но некоторые все-таки имеют особенности и оптимизированы для той или иной области. Например, изначально <a href="http://en.wikipedia.org/wiki/Flash_Video">flv</a> контейнер был разработан для облегчения встраивания его в swf файлы и способности нести screenshare кодек, созданный специально для <a href="http://en.wikipedia.org/wiki/Screencast">передачи видеопотока с монитора компьютера</a>. Также, flv содержит мета-информацию с индексом кадров, что удобно для &#8220;перемотки&#8221; видео при отдачи его при помощи <a href="http://en.wikipedia.org/wiki/Progressive_download">progressive http download</a>. Конечно, надо заметить, что это не единственный контейнер, позволяющий такое.</p>
<p>Это нас подводит к вопросу <a href="http://en.wikipedia.org/wiki/Streaming_media">потокового вещания</a> через интернет. Т.к. далеко не все маршрутизаторы готовы участвовать в <a href="http://en.wikipedia.org/wiki/Internet_Group_Management_Protocol">IGMP</a>, то <a href="http://en.wikipedia.org/wiki/IP_multicast">мультикаст</a> в интернете не работает. Поэтому для каждого клиента выделается отдельный поток данных и если даже несколько клиентов из одной сети захотят смотреть один и тот же синхронизированный поток, по сети уйдет несколько одинаковых экземпляров данных, по одному экземпляру на каждого.</p>
<p>Задача сервера потокового вещания &#8212; отдавать данные со скоростью, достаточной для просмотра, по возможности принимать команды старт, стоп, перемотка и отдавать поток соответственно. Поток отдается одним из потоковых протоколов, например, RTP/RTSP или RTMP. Потоковый сервер ничего не &#8220;знает&#8221; о самих медиа-данных, кроме мета-информации, по которой он может связать номера кадров с позицией в файле. У потокового сервера нет кодеков, поэтому, как он получил битовый поток из файла с контейнером, так он его и отдает.</p>
<p>Так что, потоковый протокол в некотором роде является контейнером для медиа-данных.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/02/16/%d0%ba%d0%be%d0%b4%d0%b5%d0%ba%d0%b8-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%b9%d0%bd%d0%b5%d1%80%d1%8b-%d0%bf%d0%be%d1%82%d0%be%d0%ba%d0%b8/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Java Logging: история кошмара</title>
		<link>http://blog.gramant.ru/2011/02/07/java-logging-nightmare-history/</link>
		<comments>http://blog.gramant.ru/2011/02/07/java-logging-nightmare-history/#comments</comments>
		<pubDate>Mon, 07 Feb 2011 07:56:07 +0000</pubDate>
		<dc:creator>Сергей Нековаль</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[commons-logging]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[java.util.logging]]></category>
		<category><![CDATA[JUL]]></category>
		<category><![CDATA[log4j]]></category>
		<category><![CDATA[logback]]></category>
		<category><![CDATA[logging]]></category>
		<category><![CDATA[SLF4J]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=753</guid>
		<description><![CDATA[Вступление Тернист и извилист путь Java-платформы к правильному способу записи строчек в лог-файлы. История logging в Java довольно познавательна в плане изучения особенностей Open Source, в том числе его взаимодействия с корпорациями и единичными программистами. Я собираюсь рассказать столько, сколько возможно, об истории развития Java logging, а также о том, к чему все пришло и [...]]]></description>
			<content:encoded><![CDATA[<h2>Вступление</h2>
<p>Тернист и извилист путь Java-платформы к правильному способу записи строчек в лог-файлы. История logging в Java довольно познавательна в плане изучения особенностей Open Source, в том числе его взаимодействия с корпорациями и единичными программистами. Я собираюсь рассказать столько, сколько возможно, об истории развития Java logging, а также о том, к чему все пришло и как жить дальше.<span id="more-753"></span> Мой анализ ситуации будет довольно субъективен про причине того, что logging &#8211; это всегда дело вкуса, а вкусы у меня сформировались свои, особые. Я думаю, что это будет познавательно не сколько в плане каких-то технических особенностей всего зоопарка logging frameworks, а в плане политики и психологии разработчиков в модели Open Source.</p>
<h2>Начало</h2>
<p>Понятно, что любая logging-библиотека должна позволять как минимум печатать строку на консоль/в лог-файл.</p>
<p>В начале был, конечно <code>System.err.println</code>. Кроме того, первая версия Servlet API имела в составе функцию <code>log</code> (впрочем, довольно примитивную).</p>
<p>Одним из вариантов более продвинутых решений в 1999 году был проект Avalon (и подпроекты, которые назывались Excalibur и Fortress), который помимо сервисов DI предлагал интерфейс LogEnabled. В компонент, который объявлял себя LogEnabled, инжектировался (я применяю это слово вместо &#8220;инъектировался&#8221;, чтобы подчеркнуть его связь с DI) объект типа Logger, куда можно было писать: а) строки б) exceptions. Подход этот по тем временам казался свежим и новаторским, однако с современной точки зрения это чистой воды идиотизм и over-engineering. Использовать DI для логгирования никакого смысла нет, и статический экземпляр этого самого Logger вполне бы всех устроил. В Avalon же приходилось думать, куда этот проклятый Logger сохранить и что же делать, если класс не использует DI (т.е. не управляется контейнером), а логгировать в нем очень хочется.</p>
<p>Приблизительно в 1999 появляется библиотека нового поколения &#8211; log4j. Прототип библиотеки был разработан IBM (еще в эпоху, когда голубой гигант пытался втиснуть Java в OS/2), затем эстафету подхватил ASF. Продукт был уже гораздо более продуманный и обкатанный на реальных нуждах. Вообще надо сказать, что серверным приложениям на Java к тому моменту исполнилось всего где-то годик, а логгирование всегда было востребовано именно на сервере. За это время Java-сообщество начало постепенно понимать, что и как им нужно.</p>
<p>log4j разделил понятие логгера или <em>категории</em> (т.е. область приложения, которая хочет записать в лог), собственно записи в лог, которую осуществляют так называемые <em>appenders</em>, и форматирования записей (<em>layout</em>). Конфигурация log4j определяет, какие appenders к каким категориям прикрепляются и сообщения какого уровня (<em>log level</em>) попадают в каждый appender.</p>
<p>Краеугольный камень log4j &#8211; это иерархичность категорий. Например, можно логгировать все сообщения из <code>org.hibernate</code> и заглушить всё из <code>org.hibernate.type</code>. Через некоторое время де-факто установилась практика соответствия иерахии категорий и иерархии пакетов в приложении.</p>
<p>Иерархия категорий позволяет довольно эффективно отсекать лишние сообщения, поэтому log4j работал чрезвычайно шустро. Кстати, принципиальной для логгеров является не столько скорость записи, сколько скорость фильтрации ненужного (а ненужного обычно более 90%) и форматирование.</p>
<p>Принципы, заложенные в log4j, были довольно удачно портированы на другие языки: log4cxx, log4net (и свежий детеныш &#8211; log4php). Стандартный пакет logging в Python 2.x представляет собой переработанный log4j (с небольшой добавкой других библиотек).</p>
<p>Итак, резюмируем. Удачная архитектура, понятная схема конфигурирования, принцип <a href="http://logging.apache.org/log4j/1.2/faq.html#a1.2">fail-safe</a> &#8211; почему бы не включить такую замечательную библиотеку в состав платформы? </p>
<h2>Java Logging API</h2>
<p>На деле все получилось странно. IBM, в недрах которой возник log4j, оказалась довольно шустрой в вопросах формирования нового JSR47 (Java Logging API). В частности, ответственный за JSR47 товарищ Graham Hamilton решил взять за основу <em>не log4j</em>, а <em>оригинальный IBM logging toolkit</em>. Причем logging toolkit был использован на полную катушку: совпадали не только имена всех основных классов, но и их реализации; код старались допиливать как можно меньше, видимо, чтобы успеть к очередному релизу платформы. Впрочем, концептуально это было очень похоже на log4j, только вместо appenders это называлось handlers, а вместо layout был formatter. </p>
<p>Поскольку основное назначение JSR47 &#8211; определять <strong>API, а не реализацию</strong>, доступных (по умолчанию в платформе) средств вывода было всего 4 (в log4j более 10), а средства форматирования были настолько бедны, что практически сразу приходилось делать свои formatter-ы, поскольку готовых не хватало. JSR47 предлагал использовать конфигурацию в виде <code>.properties</code>, причем в скобках отмечалось, что в файле можно описать не все. Таким образом, при усложнении конфигурации программист неожиданно обнаруживал, что опять требуется писать код, т.к. в виде <code>.properties</code> его конфигурация нереализуема.</p>
<p>Нельзя сказать, чтобы JSR47 проигрывал в производительности. Местами он обгонял log4j за счет поддержания в памяти специального представления своей конфигурации (что, кстати, одновременно усложняло эту самую конфигурацию). Однако, как выяснилось, JSR47 в обязательном порядке собирал так называемую Caller Information, то бишь &#8220;откуда логгируется данное сообщение&#8221;. Получение Caller Information &#8211; операция довольно дорогостоящая, протекает она с использованием Native-кода. Опытные дяди из log4j это знали, поэтому предоставляли эту возможность с оговоркой &#8220;лучше не включайте&#8221;.</p>
<p>Разработчики log4j выступили с открытой <a href="http://www.ingrid.org/jajakarta/log4j/jakarta-log4j-1.1.3/docs/critique.html">петицией</a>, где потребовали &#8220;снять JSR47 с конвейера&#8221;, пока он еще не попал в состав платформы. Петицию подписали более 100 человек&#8230; Однако было уже поздно. Следующий релиз JDK был утвержден и платформа понеслась в будущее с рудиментарным <code>java.util.logging</code>, или сокращенно JUL. Новый логгинг был настолько неразвит и неудобен, что использовать его решились только в нескольких серверах приложений (среди них Resin и Jetty). Sun, впрочем, отреагировала на петицию и большинство крупных проблем оригинального JSR47 постепенно были устранены. Тем не менее, эти манипуляции походили скорее на установку подпорок к деревянному мосту, которые ну никак не сделают этот мост железобетонным. Разработчики log4j сделали <a href="http://svn.apache.org/repos/asf/logging/log4j/tags/v_1_2beta3/docs/lobby.html">реверанс</a> в сторону Sun, заметив, однако, что степень кривизны JUL все еще довольно высока. Помимо всего прочего, лицензия JDK 1.4 <strong>не позволяла</strong> использовать log4j в качестве реализации JUL. Последний поезд ушел.</p>
<p>Не будучи способным поддержать большое число лог-писателей (т.е. handlers), JUL выпендрился, определив неимоверное число уровней логгирования. Например, для отладочных сообщений существовало аж 3 уровня &#8211; FINE, FINER и FINEST. Видя всё это, разработчики зачастую совершенно не понимали, какой же из трех уровней, чёрт возьми, надо использовать.</p>
<p>Java-сообщество было совершенно дезоориентировано появлением &#8220;стандартного&#8221; логгинга параллельно с популярным, стабильным и развивающимся log4j. Никто не понимал, кто из них двоих жилец. Нередки были ситуации, когда в проекте было собрано несколько библиотек, каждая из которых использовала свой логгинг и свои настройки, записывая совершенно вразнобой свои лог-файлы.</p>
<p>Разумеется, сообщество попыталось исправить эту проблему. Началась <strong>оберточная эпидемия</strong>. Или, я бы даже сказал, пандемия.</p>
<h2>Wrapper Hell</h2>
<p>Когда вы подключаете несколько библиотек и пытаетесь соединить их логи в одно целое (а код модифицировать нельзя), это будет называться Adapter. Были написаны переходники из JUL в log4j и наоборот. К сожалению, переходники по функционалу являются &#8220;наименьшим общим кратным&#8221;. Даже когда в log4j появилась поддержка контекста (NDC и MDC), при переливании в JUL она терялась. Хуже того, JUL работал только начиная с JDK 1.4, в то время как неимоверное количество enterprise-приложений все еще сидело на 1.3. В итоге, сообщество стало одержимо идеей создания &#8220;общего стандарта де-факто&#8221;, который бы все стали дружно употреблять и который работал всегда и везде.</p>
<p>Приблизительно в 2002 из группы Jakarta выделился проект под названием commons-logging (JCL = Jakarta Commons Logging). Фактически это была обертка всех существующих на тот момент средств логгинга. Предлагалось писать приложения так, что они обращались к обертке (интерфейсу под названием <code>Log</code>), которая выбирала &#8220;подходящую&#8221; систему логгинга и сама к ней подключалась. Обертка была бедновата фунционально и никаких дополнений к существующим средствам логгинга не вносила.</p>
<p>Как же <em>автоматически</em> выбиралась подходящая система логгирования? А вот это самое интересное. Во-первых, можно было задать ее явным образом размещением специального <code>commons-logging.properties</code>-файла где-нибудь в CLASSPATH. Во-вторых, через системное свойство (что, очевидно, никто делать не будет). В-третьих, если где-то в CLASSPATH обнаруживался log4j, то он автоматически задействовался. Таким же методом разыскивались реализации всех остальных библиотек, всегда подключалась первая найденная.</p>
<p>Красиво! Ну то есть <em>было бы</em> красиво, если бы весь софт в мире использовал бы commons-logging. Тогда можно было спокойно собрать JARы, положить в сервер приложений, а там уж JCL подхватит логгинг данного сервера приложений и вуаля!</p>
<p>На самом деле, как выяснилось, куча софта использует обычно &#8220;любимый логгинг своего разработчика&#8221;. Это означает, что совершенно произвольная библиотека может в виде зависимости подтянуть, например, log4j, который таким образом попадет в CLASSPATH и неожиданно переключит JCL на использование log4j. Еще хуже с <code>commons-logging.properties</code>. Если какой-нибудь деятель додумывался запихнуть его в свой JAR, то при подключении этого JAR-а &#8211; сами понимаете &#8211; пиши пропало. Особую пикантность ситуации придавало то, что совершенно непонятно было, из какого именно JAR-а приехала инфекция. Иногда помогал перебор всех JAR-ов в алфавитном порядке. Иногда бубен.</p>
<p>Полная непредсказуемость выбора логгинга оказалась главной и очень веселой особенностью JCL. Группа log4j разразилась гневной статьей <a href="http://articles.qos.ch/thinkAgain.html">Think again before adopting the commons-logging API</a>, где предлагала остановить эпидемию и сосредоточить внимание на доработке существующего решения &#8211; log4j.</p>
<p>К сожалению, было уже поздно. С подачи Jakarta на commons-logging были переведены сотни, а затем тысячи библиотек. В их числе были Hibernate, Spring, Tomcat. После чего многочисленных пользователей этих библиотек захлестнула волна проблем, в целом описываемых как <strong>ClassLoader hell</strong>. В серверах приложений используется довольно сложная иерархия ClassLoader-ов, причем зачастую с серьезными отклонениями от стандарта J2EE. В этих условиях <em>иногда</em> JCL инициализируется дважды, причем неправильно, приводя к совершенно мистическим stack traces, не позволяющим даже заподозрить, что проблема в лог-обертке.</p>
<p>Почему, собственно говоря, Open Source сработал таким странным образом, породив на свет данное извращение? Почему разработчики не решились просто так взять и использовать другой зрелый и популярный Open Source продукт &#8211; log4j? Дело здесь, возможно, в некоторой инертности сообщества, привыкшего идти на поводу либо у ASF (а группа Jakarta, породившая данный кошмар, есть часть ASF), либо у Sun. Как только образуется критическая масса проектов, использующих JCL, все остальные (и не самые глупые люди, так ведь, Gavin King?) начинают использовать JCL (ибо Apache &#8211; это круто!). Это в целом  напоминает броуновское движение, где такие бренды как Apache или Sun способны создавать области низкого давления, куда устремляются миллионы разработчиков. В случае JCL &#8220;история успеха&#8221; описана в <a href="http://radio-weblogs.com/0122027/2003/08/15.html">блоге Rod Waldhoff</a> (один из разработчиков так называемых Jakarta Commons) в 2003 году.</p>
<h2>Новый виток прогресса</h2>
<p>Итак, где-то на 2004 год имеем в комплекте:</p>
<ol>
<li>Стабильный и функционально развитый log4j</li>
<li>Унылый java.util.logging</li>
<li>Проблемный commons-logging</li>
<li>Несколько мелких логгеров, недостойных упоминания</li>
</ol>
<p>Отметим, что в проекте log4j в это время преобладали консервативные настроения. Особое внимание уделялось вопросу совместимости со старыми JDK. Вроде бы начинается разработка новой ветки log4j &#8211; 1.3.x. Эта версия &#8211; своего рода компромиссное решение: да, хочется новый функционал, да, хочется поддерживать обратную совместимость, да, попробуем угодить и нашим и вашим. А тем временем на подходе JDK 1.5 с varargs, JMX extensions и кучей других подарков. В команде log4j началось брожение умов. Отпочковывается ветка 2.x &#8211; несовместимая с основной веткой 1.2.x и созданная специально для JDK 1.5. Java-сообщество изнывает в нетерпении. Происходит вроде бы как <em>что-то</em>. Но что именно, не понять &#8211; log4j 2.0 по-прежнему остается недостижимой альфой, log4j 1.3 дико глюкав и не обеспечивает обещанной drop-in совместимости. И только ветка 1.2 по-прежнему стабильна и жива-здорова, прыгнув <em>за несколько лет</em> &#8211; внимание! &#8211; с версии 1.2.6 до 1.2.12.</p>
<p>Где-то в 2006 году один из отцов-основателей log4j &#8211; Ceki Gülcü &#8211; решает выйти из стремительно тухнущей команды. Так появляется на свет очередная &#8220;обертка всего&#8221; под названием SLF4J (Simple Logging Facade for Java). Теперь это обертка вокруг: log4j, JUL, commons-logging и нового логгера под названием logback. Как видно, прогресс быстро дошел до стадии &#8220;обертка вокруг обертки&#8221;. <i>Нетрудно спрогнозировать, что по той же схеме число обертываемых библиотек будет расти как факториал.</i> Однако SLF4J предлагает и другие прочие выверты. Это специальные binary-переходники: из log4j в SLF4J, из commons-logging в SLF4J и тому подобное. Делаются такие переходники для кода, исходники которого недоступны; при этом они должны подменить оригинальные JAR-ы лог-библиотек. Не берусь представить себе, какая каша при этом образуется, но если очень хочется, то можно и так.</p>
<p>При всей моей ненависти к оберткам, положа руку на сердце, SLF4J &#8211; хорошо сделанный продукт. Были учтены все недостатки предшественников. Например, вместо шаманских плясок с поиском классов в CLASSPATH придумана более надежная схема. Теперь вся обертка делится на две части &#8211; API (который используется приложениями) и Реализация, которая представлена отдельными JAR-файлами для каждого вида логгирования (например, <code>slf4j-log4j12.jar</code>, <code>slf4j-jdk14.jar</code> и т.д.). Теперь достаточно только подключить к проекту нужный файл Реализации, после чего &#8211; опа! весь код проекта и все используемые библиотеки (при условии, что они обращаются к SLF4J API) будут логгировать в нужном направлении.</p>
<p>Функционально SLF4J поддерживал все современные навороты типа NDC и MDC. Помимо собственно обертывания вызовов, SLF4J предлагал небольшой, но полезный бонус при форматировании строк. Бонус тут в следующем. В коде часто приходится печатать конструкции вида:<br />
<code><br />
LOG.debug("User " + user + " connected from " + request.getRemoteAddr());<br />
</code><br />
Помимо собственно печати строки, тут неявно произойдет преобразование <code>user.toString()</code> с последующей конкатенацией строк. Все бы ничего. В отладочном режиме скорость выполнения нас не очень волнует. Однако даже если мы выставим уровень, скажем, в INFO, окажется, что конструирование строки все равно будет происходить! Никаких чудес: строка конструируется <em>перед</em> вызовом <code>LOG.debug</code>, поэтому log4j не имеет возможности как-то это контролировать. Если представить, что этот <code>LOG.debug</code> размещен в каком-то критическом внутреннем цикле&#8230; в общем, так жить нельзя. Разработчики log4j предложили обрамлять отладочный код так:<br />
<code><br />
if (LOG.isDebugEnabled()) {<br />
    LOG.debug("User " + user + " connected from " + request.getRemoteAddr());<br />
}<br />
</code></p>
<p>Нехорошо получается. По идее все эти проблемы должна брать на себя сама logging-библиотека. Эта проблема стала просто ахиллесовой пятой log4j. Разработчики вяло реагировали на пинки, рассказывая, что в logging-вызовы теперь можно еще добавить объект (ровно один!), да еще описать, как этот объект будет записан в лог с помощью интерфейса <code>ObjectRenderer</code>. По большому счету, все это были отмазки и полумеры.</p>
<p>SLF4J не был стиснут рамками совместимости со старыми версиями JDK и API, поэтому с ходу предложил более изящное решение:<br />
<code><br />
    LOG.debug("User {} connected from {}", user, request.getRemoteAddr());<br />
</code></p>
<p>В общем-то, все просто. В данной строке <code>{}</code> &#8211; это ссылки на параметры, которые передаются отдельно. Преобразование параметров в строку и окончательное форматирование лог-записи происходит <em>только</em> при установленном уровне DEBUG. Параметров можно передавать много. Работает! Не надо писать обрамляющий if и прочую тупость!</p>
<p>В скобках надо отметить, что данную возможность также совершенно неожиданно реализовал язык Groovy, где есть понятие GString, т.е. строка вида <code>"User ${user} connected from ${request.getRemoteAddr()}"</code>, которая неявно связана с несколькими контекстными переменными (здесь это <code>user</code>, <code>request</code>), причем вычисление строки происходит <em>отложенным образом</em>. Это очень удобно для таких лог-библиотек как log4j &#8211; можно получить на вход GString, а затем или выбросить его без вычисления, или все-таки преобразовать в нормальную (статическую) строку &#8211; String.</p>
<p>Короче говоря, SLF4J был сделан грамотно, с заделом на будущее. Это вызвало серьезный рост его популярности среди сообщества: сейчас SLF4J используют такие значимые проекты, как Jetty, Hibernate, Mina, Geronimo, Mule, Wicket, Nexus&#8230; в общем, практически все неудачники, зависшие в свое время на commons-logging, перешли на SLF4J. Интересно, что мешало усовершенствовать commons-logging до нужного состояния много лет назад? Но таковы реалии Open Source &#8211; развитие софта в нем происходит скорее революционно, чем эволюционно.</p>
<p>Одновременно с SLF4J был подан к столу совершенно новый логгер &#8211; Logback. Он был сделан человеком, который на логгировании собаку съел, и на поверку действительно оказался хорошим продуктом. Logback был изначально заточен под JDK 1.5+, одним махом избавившись от всех старческих болезней обратной совместимости, свойственных проекту log4j. А это значит &#8211; varargs, <code>java.util.concurrent</code> и <a href="http://logback.qos.ch/reasonsToSwitch.html">прочие прелести</a>. Например, за счет встроенной системы runtime-фильтрации можно менять уровень логгирования в зависимости от пользовательской сессии, разбрасывать пользователей по разным лог-файлам и прочее, прочее.</p>
<p>Я подкину горчички в идиллию, нарисованную автором. Большинство этих возможностей можно реализовать в виде дополнительных appender-ов к log4j. Придется искривить и подпилить конфигурацию, это сложнее, но &#8211; факт, что переходить для этого на новый логгер не_обязательно. Таким образом, все рекламируемые Logback фишки &#8211; удобные, но не уникальные.</p>
<p>Что касается сообщества, то оно к Logback относится с осторожностью. Во-первых, за несколько лет он добрался до версии 0.9.x, а это пугает некоторых программеров. Во-вторых, Logback не находится ни под зонтиком Apache, ни в области действия Sun. Это смущает людей щепетильных. В-третьих, автору надо кушать, поэтому за некоторые довески к Logback и поддержку он требует денег. Это иногда отпугивает студентов. Помимо всего прочего, Logback имеет довольно сложную двойную лицензию (LGPL/EPL), в то время как log4j &#8211; универсальную лицензию Apache. Для библиотек и вообще redistributable софта лицензирование является очень тонким моментом.</p>
<p>По большому счету, Logback на сегодняшний день &#8211; вершина эволюции. Помимо Logback появилось уже с десяток новых logging-библиотек, но с большой вероятностью ни одна из них не выживет. Подводя итоги, ситуация на данный момент следующая:</p>
<ul>
<li><strong>log4j</strong> &#8211; используют подсевшие на него изначально и не видящие необходимости перехода.
<li><strong>JUL</strong> &#8211; тихо умирающий стандарт. Все, кто изначально пытался его использовать, переезжают на Logback.
<li><strong>commons-logging</strong> &#8211; обычно задействован в legacy-библиотеках, которые очень боятся причинить неудобства пользователем, переехав на что-нибудь получше.
<li><strong>SLF4J</strong> &#8211; очень популярен в библиотеках. Многие переехали на него, не выдержав ужасов commons-logging.
<li><strong>Logback</strong> &#8211; обычно современные high-performance серверы, которых не устраивает log4j.
</ul>
<p>Я уже говорил, что Open Source сообщество имеет тенденцию стекаться к &#8220;центрам тяжести&#8221;. Сейчас таким центром тяжести выступает скорее SLF4J в силу &#8220;универсальности&#8221;. Относительная популярность SLF4J в какой-то степени гарантирует от появления новых оберток. Число проектов, использующих SLF4J, уже является достаточным для накопления &#8220;критической массы&#8221;. У Logback (того же автора, заметьте) такой критической массы нет. (Кстати, log4j по прежнему обещает нам золотые горы и версию 2.0, однако воз и ныне там.) Думаю, если Logback усмирит свою гордыню и двинется в Apache, его позиции сильно улучшатся.</p>
<h3>Заключение</h3>
<p>Интересно посмотреть на историю вопроса под углом психологии программистов. Ведь в принципе всё это спиральное (и вроде как <em>прогрессирующее</em>!) движение &#8211; бесконечный &#8220;reinvent the wheel&#8221;. То есть из двух вариантов &#8220;доработать существующее&#8221; и &#8220;сделать свое&#8221; всегда выбирался второй. Поэтому ни один из упомянутых проектов не выбился в безусловные лидеры (в те самые стандарты &#8220;де-факто&#8221;). Вместо этого разработчики были в разное время &#8220;нашинкованы&#8221; на разные проекты и действовали раздельно, вместо того, чтобы действовать сообща. Хотя не факт, что все авторы смогли бы работать в одной упряжке. Тут действовали и политические моменты (вспомним, как Graham Hamilton любил IBM), и просто банальные ссоры в команде. Стремление же участников Jakarta Commons обеспечить сообществу &#8220;свободу выбора&#8221; вообще обернулось для сообщества длительной &#8220;эпидемией оберток&#8221;. </p>
<p>В общем-то, все эти пороки типичны для открытого сообщества. Эта более чем 10-летняя история также показывает, насколько ошибочно распространенное сейчас мнение, что Sun как будто бы что-то решало в Java-сообществе. Мы видим, что многие вещи происходили вопреки Sun и независимо от Sun. Одним словом, интересно, как оно пойдет дальше. В одном я уверен &#8211; проекты приходят и уходят, люди не меняются <img src='http://blog.gramant.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/02/07/java-logging-nightmare-history/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Особенности написания java-приложений для Android (часть 2)</title>
		<link>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-2/</link>
		<comments>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-2/#comments</comments>
		<pubDate>Fri, 04 Feb 2011 15:25:55 +0000</pubDate>
		<dc:creator>Дмитрий Родичев</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[java]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=808</guid>
		<description><![CDATA[Как было сказано ранее, во второй части статьи речь пойдет о типовых приемах написания приложений для android. Однако, вначале несколько слов о Java API, имеющимся в распоряжении разработчика. Разработчики платформы реализовали значительное число классов из J2SE. Их список расширяется и обновляется с выходом каждого нового релиза. Так, для последней на момент написания этой статьи версии [...]]]></description>
			<content:encoded><![CDATA[<p>Как было сказано ранее, во второй части статьи речь пойдет о типовых приемах написания приложений для android. Однако, вначале несколько слов о Java API, имеющимся в распоряжении разработчика.<span id="more-808"></span></p>
<p>Разработчики платформы реализовали значительное число классов из J2SE. Их список расширяется и обновляется с выходом каждого нового релиза. Так, для последней на момент написания этой статьи версии платформы 2.3, в распоряжении программистов имеются классы из пакетов (перечислены не все пакеты):</p>
<p><code>java.io<br />
java.lang<br />
java.math<br />
java.net<br />
java.nio<br />
java.security<br />
java.sql<br />
java.text<br />
java.util (включая concurrent, atomic, locks, regex, zip)<br />
javax.crypto<br />
javax.net<br />
javax.net.ssl<br />
javax.security.auth<br />
javax.security.cert<br />
javax.sql<br />
javax.xml (включая поддержку для SAX, DOM, XSLT, XPATH)<br />
org.w3c.dom<br />
org.xml.sax</code></p>
<p>Имеется поддержка для работы с форматом JSON и для создания JUNIT-тестов.</p>
<p>Особо следует отметить реализацию пакета org.apache.http и его подпакетов, что позволяет писать клиентскую часть http приложений с минимальными трудозатратами на обработку особенностей http протокола.</p>
<p>Пакеты с именем android.* предоставляют программисту доступ к различному оборудованию и содержат классы, отражающие особенности программирования для этой платформы. Кроме этого, ряд этих пакетов дублирует функциональность уже упомянутых пакетов, но, с некоторой адаптацией для платформы android (например, android.net.http). Другие добавляют классы и методы, которые, отсутствуют в J2SE (см. android.sax , android.text.* , android.util).</p>
<p>Теперь &#8211; о разработке приложений под android. Я попытаюсь рассказать, какие приложения можно написать под android и как это сделать, не перегружая текст блога примерами законченных приложений, достаточное число которых имеется на сайте google и множестве около-google сайтов.</p>
<p><strong>Приложение 1</strong></p>
<p>Приложение, вся работа которого состоит во взаимодействии с пользователем и которое не имеет долгих фоновых задач, является наиболее простым типом приложения. В этом случае приложение представляет собой набор Activity, одни из которых вызывают другие. Поскольку вызов — это обработка некоторого Intent (созданного другой Activity) и происходит в главной нити, которая в том числе обрабатывает и события, генерируемые пользовательским интерфейсом, обработка должна происходить гарантированно быстро. Так, будет неправильно в обработчике пытаться получить информацию с сетевого сервера или обратиться к сетевой файловой системе, используя блокирующий вызов — если сервер окажется недоступным, пользовательский интерфейс зависнет. В качестве примера вызова одной Activity из другой с передачей вызываемой Activity некоторой информации и получением от нее ответа рассмотрим CallerActivity, которая имеет параметры, устанавливаемые в CalledActivity.</p>
<pre>public class CallerActivity extends Activity {
    // ....
    // этот метод может, например, вызываться из обработчика нажатия кнопки
    // "Изменить параметры"
    void someMethod(int requestCode) {
        // Создаем объект для передачи вызываемой Activity. Нужно указать
        // экземпляр(!) вызывающей и класс(!) вызываемой Activities
        Intent intent = new Intent(this, CalledActivity.class);
        // Формируем набор параметров для передачи. Это могут быть не только
        // строки. Подробности смотри в javadoc.
        String[] params = { "Edit ", "parameters please." };
        // "цепляем" параметры к передаваемому объекту
        intent.putExtra(INPUT_PARAMS, params);
        // вызываем CalledActivity. После того, как главная нить получит
        // обратно управление, будет создан экземпляр класса CalledActivity и
        // вызван его метод onCreate(), который сформирует новый экран
        startActivityForResult(intent, requestCode);
    }

    // Этот метод будет вызываться, когда CalledActivity возвратит управление.
    // Важно, что "data" это не тот Intent объект, который передавался при ее
    // вызове, но requestCode будет тот же.
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            if (data != null) {
                // получить данные, возвращенные CalledActivity
                String[] params = (String[]) data.getExtras()
                        .get(OUTPUT_PARAMS);
                // сохранить параметры в приватный файл приложения
                // ....
            }
        }
    }
    // ....
}

public class CalledActivity extends Activity {
    // ....
    // Этот метод вызывается при создании данной активити
    public void onCreate(Bundle savedInstanceState) {
        // нужно обязательно вызвать конструктор суперкласса
        super.onCreate(savedInstanceState);
        // ....
        // получить переданный из caller-а объект
        Intent data = getIntent();
        // получить переданные параметры
        String[] params = (String[]) data.getExtras().get(INPUT_PARAMS);
        // ....
    }

    // этот метод может, например, вызываться из обработчика нажатия кнопки
    // "Сохранить параметры"
    private void returnResult() {
        // создаем носитель для возвращаемых значений
        Intent intent = new Intent();
        // Формируем набор возвращаемых значений
        String[] params = { "Done" };
        // "цепляем" параметры к передаваемому объекту
        intent.putExtra(OUTPUT_PARAMS, params);
        // устанавливаем статус, который потом будет анализироваться в
        // CallerActivity
        setResult(Activity.RESULT_OK, intent);
        // возвращаем управление. После того, как главная нить получит
        // обратно управление, экземпляр класса CallerActivity будет извлечен из
        // стека. Затем будет вызван его метод onActivityResult, который
        // обработает переданную из CalledActivity информацию
        finish();
    }
    // ....
}</pre>
<p>INPUT_PARAMS и OUTPUT_PARAMS — некоторые константы, определенные в приложении.</p>
<p>Весь пользовательский интерфейс создается в методах onCreate каждой Activity, при этом рекомендуется использование разметок (layout). Разметка подключается в этом методе при вызове метода setContentView(R.layout.ZZZ); Ожидается, что файл ZZZ.xml содержится в каталоге res/layout проекта. Важно то, что интерфейс R — это генерируемый интерфейс, который размещается в каталоге gen проекта, а не одноименный класс R из пакета android. Поэтому при использовании конструкций вида R.id.* до перекомпиляции проекта они могут рассматриваться редактором IDE как ошибочные.</p>
<p>Каждый раз, когда пользователь нажимает кнопку «Назад», текущая Activity завершается и управление передается вызвавшей ее Activity (при условии, что при вызове не была порождена новая задача — task). Таким образом вызвавшая ее Activity может и не получить ожидаемых данных и должна уметь обрабатывать эту ситуацию. Если данная Activity находится на дне стека, то приложение завершается.</p>
<p>Activity может быть завершена или перезапущена системой. Наиболее простой пример — изменение ориентации экрана. В этом случае текущая Activity завершается с вызовом ее метода onDestroy() и рестартует (но уже с новыми параметрами экрана) с вызовом метода onCreate(). Замечу, что вызов onDestroy не гарантируется системой, если приложение выгружается из-за нехватки памяти.</p>
<p><strong>Приложение 2</strong></p>
<p>Теперь рассмотрим архитектуру приложения, которое должно выполнять некоторую фоновую задачу, даже, если на экране выведена Activity другого приложения. Это может быть, например, периодическое получение с сервера некоторой информации и ее обработка. Главная особенность этой архитектуры состоит в том, что фоновая задача выполняется в нити, созданной сервисом. Итак, приложение состоит из:</p>
<ul>
<li>Activity, задача которой состоит в том, чтобы позволить пользователю управлять процессом. Она запускается пользователем при необходимости и может быть завершена пользователем или системой. Эта компонента получает доступ к сервису (запуская его, если он еще не запущен) с помощью метода bindService и передает ему команды пользователя.</li>
</ul>
<ul>
<li>сервиса, который запускает и останавливает фоновую нить.</li>
</ul>
<p>Как уже отмечалось, система может в любой момент остановить и выгрузить из памяти любую компоненту, но любой сервис будет выгружен только после выгрузки всех неактивных (не видимых на экране) Activity. То что нить запускается из сервиса, гарантирует ей существенно меньшую вероятность случайной выгрузки, чем когда бы она запускалась из Activity.</p>
<p>Далее приведено словесное описание и код одного из возможных сценариев. Чтобы избежать слишком длинных трудновоспринимаемых фраз, там, где это возможно, некоторые слова пропущены. Надеюсь обращение к коду позволит устранить все двусмысленности.</p>
<p>Для работы с сервисом Activity нужно:</p>
<ol>
<li>создать экземпляр наследника класса Handler, в котором будет обрабатываться ответ сервиса (конечно, следовало бы сказать в &#8220;методе &#8230; класса&#8221; и т.п., но, как я уже отметил, для краткости подобные уточнения будут опускаться).</li>
<li>создать экземпляр класса Messenger и передать при создании его конструктору созданный экземпляр Handler. Этот объект будет передаваться сервису, чтобы сервис мог вернуть данные.</li>
<li>создать экземпляр класса, реализующего интерфейс ServiceConnection. Методы этого интерфейса будут вызываться в момент установки и разрыва соединения с сервисом. В методе установки соединения можно получить и запомнить объект Messenger, для посылки команд сервису.</li>
<li>в методе onCreate, например, следует вызвать метод bindService и передать ему в качестве параметра созданный экземпляр класса ServiceConnection</li>
<li>когда требуется послать команду сервису следует использовать метод send объекта Messenger из п.3. Для передачи данных сервису нужно создать и заполнить экземпляр объекта Bundle, поместить его объект Message и передать этот Message в качестве параметра вышеупомянутому методу send.</li>
</ol>
<p>Сервис при этом может использовать следующий шаблон:</p>
<ol>
<li>создать экземпляр наследника класса Handler, в котором будут обрабатываться команды и возвращаться ответ. Соответствующий метод этого класса будет получать объект Message, сформированный Activity.</li>
<li>создать экземпляр класса Messenger и передать при создании его конструктору созданный экземпляр Handler.</li>
<li>реализовать метод onBind, возвращающий созданный объект Messenger</li>
<li>для возврата данных Activity, сервису следует вызвать метод send объекта Messenger. Этот Messenger содержится в поле replyTo объекта Message, полученного экземпляром Handler из п.1.</li>
</ol>
<p>Вот пример кода сервиса, в котором реализованы все указанные шаги (обратите внимание на обработку сообщения TEST_CODE):</p>
<pre>public class TestService extends Service {
    public static final int TEST_CODE = 1;
    public static final int START_THREAD = 2;
    public static final int STOP_THREAD = 3;
    // объект для обработки команд от Activity и посылки ответа
    private Messenger mMessenger = new Messenger(new Handler() {
        public void handleMessage(Message msg) {
            try {
                switch (msg.what) {
                case START_THREAD:
                    // start background thread
                    break;
                case STOP_THREAD:
                    // stop background thread
                    break;
                case TEST_CODE:
                    Bundle bundle = new Bundle();
                    // извлекаем принятые от Activity данные и формируем тестовые данные
                    bundle.putSerializable("date",
                        msg.getData().getSerializable("pref") + " : " + new Date());
                    // создаем экземпляр класса Message
                    Message m = Message.obtain(null, TEST_CODE);
                    // помещаем тестовые данные в message
                    m.setData(bundle);
                    // используя содержимое поля replyTo посылаем данные обратно Activity
                    msg.replyTo.send(m);
                    break;
                default:
                    super.handleMessage(msg);
                }
            } catch (Exception e) {
            }
        }
    });
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}</pre>
<p>А вот соответствующая Activity (не относящиеся к делу части кода опущены для краткости):</p>
<pre>public class TestActivity extends Activity {
    // объект для посылки команд сервису
    private Messenger mService = null;
    // объект для обработки ответов сервиса
    private Messenger mMessenger = new Messenger(new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TestService.TEST_CODE:
                    // получить данные, возвращенные сервисом и что-то с ними сделать
                    Object obj = msg.getData().getSerializable("date");
                    // ...
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    });
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
        }
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
    // посылаем команду сервису
    // подробности смотри в аналогичном коде сервиса
    private void testService() {
        Bundle bundle = new Bundle();
        bundle.putSerializable("pref", "Date");
        Message msg = Message.obtain(null, TestService.TEST_CODE);
        msg.setData(bundle);
        // заполняем поле объектом, который сервис будет
        // использовать для посылки ответа
        msg.replyTo = mMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
        }
    }
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
        // устанавливаем соединение с сервисом
        boolean res = bindService(new Intent(this, TestService.class),
                mConnection, Context.BIND_AUTO_CREATE);
        // анализ значения res - успешности соединения с сервисом
        // ...
    }
}</pre>
<p>Важно знать, что вызов send является синхронным и выполняется в главной нити. Поэтому он должен как можно быстрее возвратить управление. Нить, запускаемая сервисом для выполнения фоновой задачи, может быть как обычной нитью, так и неявной нитью, создаваемой при инициализации задачи таймера (Timer, TimerTask классы), неблокируемого nio вызова, классом HandlerThread и т.п..</p>
<p>Возможно использование и других подходов для выполнения поставленной задачи.</p>
<p><strong>Приложение 3</strong></p>
<p>Устройства имеют т.н. спящий режим, в который они входят после некоторого периода неактивности пользователя. В этом режиме все приложения, включая фоновые, останавливаются. Для приложений, которые должны выполняться, несмотря на спящий режим, в android имеется специальное API.</p>
<p>Предположим, что приложение должно посылать некоторую информацию на сервер через определенные промежутки времени, независимо от того &#8220;спит&#8221; устройство или нет. Здесь не будет подробно рассматриваться данный тип приложений, однако рекомендации по их реализации будут приведены.</p>
<p>Нужно решить 2 задачи. Первая &#8211; это выйти из спящего режима в определенный момент времени. Вторая &#8211; не допустить переход в спящий режим пока не будет выполнена задача (послана информация на сервер).</p>
<p>Для выполнения первой задачи следует использовать класс  android.app.AlarmManager . Этот класс позволяет пробуждаться приложению  вне зависимости от состояния устройства. При этом могут использоваться  различные стратегии повторных пробуждений.</p>
<p>Для решения второй задачи имеется класс android.os.PowerManager, который позволяет &#8220;захватить&#8221; WakeLock объект, что не позволит устройству перейти в спящий режим, пока он не будет &#8220;освобожден&#8221;.</p>
<p>Подробности смотри в javadoc  для этих классов.</p>
<p>Приложение могло бы содержать:</p>
<ol>
<li>Activity, с помощью которой пользователь управляет приложением и которая взаимодействует с сервисом и устанавливает расписание пробуждения для Broadcast receiver-а.</li>
<li>Broadcast receiver, который будет пробуждаться AlarmManager-ом в указанное время и при выборе соответствующей стратегии инициировать повторное пробуждение через определенное время.</li>
<li>Сервис, который будет вызываться из Broadcast receiver-а и запускать фоновую нить для выполнения нужной задачи. Нить при запуске должна захватывать WakeLock, чтобы предотвратить &#8220;засыпание&#8221; устройства во время своей работы. По окончании работы нить должна передать результат работы сервису или записать в файл и освободить WakeLock.</li>
</ol>
<p>Здесь нужно разъяснить следующую особенность. Хотя на период работы метода обработки события пробуждения Broadcast receiver-а и автоматически захватывается WakeLock, он освобождается сразу после завершения этого метода. Вся обработка проходит в главной нити и должна быть завершена как можно быстрее. Именно поэтому запускаемая сервисом нить должна захватить свой WakeLock, поскольку во время ее выполнения первый лок будет уже освобожден.</p>
<p>В заключение будет кратко рассмотрен механизм логирования в android. Для вывода сообщений в лог в android используется объект android.util.Log . Он имеет метод i(), соответствующий уровню INFO, e() &#8211; уровню ERROR и т.д. Методы принимают в качестве первого параметра идентифицирующую строку (это может быть имя класса, метода или что-то еще), а в качестве второго строку сообщения. Возможно задание и третьего аргумента типа Throwable. Чтобы увидеть лог в IDE Eclipse нужно открыть окно LogCat из меню Window -&gt; Show View -&gt; Other -&gt; Android. Для production версии рекомендуется удалить все логирование. Одним из способов сделать это является добавление перед каждым оператором логирования условного if от глобальной константы. Например:</p>
<pre>if (LOG_ENABLED) { Log.e(TAG, e.getMessage()); }</pre>
<p>Для тестовой версии эта константа устанавливается в true, а для production &#8211; в false. &#8220;Правильный&#8221; компилятор должен удалить неиспользуемый код.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Особенности написания java-приложений для Android  (часть 1)</title>
		<link>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-1/</link>
		<comments>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-1/#comments</comments>
		<pubDate>Fri, 04 Feb 2011 15:16:55 +0000</pubDate>
		<dc:creator>Дмитрий Родичев</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[java]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=803</guid>
		<description><![CDATA[Настоящая статья содержит обзор особенностей, которые java-разработчику следует иметь ввиду при написании приложений для платформы Android. Первая часть посвящена ответам на вопросы, которые обычно возникают у программистов, не знакомых с android, когда им требуется написать или принять решение о написании программы для этой платформы. Вторая часть имеет более практическую направленность и содержит ряд советов, приемов, [...]]]></description>
			<content:encoded><![CDATA[<p>Настоящая статья содержит обзор особенностей, которые java-разработчику следует иметь ввиду при написании приложений для платформы Android.  Первая часть посвящена ответам на  вопросы,  которые обычно возникают у программистов, не знакомых с android, когда им требуется написать или принять решение о написании программы для этой платформы. Вторая часть имеет более практическую направленность и содержит ряд советов, приемов, шаблонов, следование которым позволит быстро написать тот или иной тип приложения.</p>
<p lang="ru-RU">Платформа Android &#8211; это основанная на ядре Linux адаптированная для работы на смартфонах система. Java &#8211; это основной язык для разработки приложений на этой платформе. <span id="more-803"></span>Имеется также возможность написания приложений на различных скриптовых языках.  Разработка приложения для android целиком на C/C++, по крайней мере официально, не поддерживается (хотя есть программы, которые преобразуют C/C++ код в java код). Когда возникает необходимость использования native кода, пишутся native библиотеки, которые затем инсталлируются на устройстве и вызываются из java кода.</p>
<p lang="ru-RU">В отличии от Java ME, Android не вводит понятий профайла и его расширений. Вопрос о работоспособности того или иного API должен решаться на этапе инсталляции. Для этого в файле конфигурации приложения имеется специальный тег <em>uses-feature</em>. Приложение может получить доступ к таким подсистемам  устройства, как видео камера, bluetooth, сеть, файловая система, телефон (звонки и sms) и др..  Версии платформы совместимы и приложения, написанные для младших версий, могут быть выполнены на устройствах с более старшими версиями. Аналогично J2ME, Android платформа может иметь дополнительное, специфичное для данного устройства API.</p>
<p lang="ru-RU">Вопросы безопасности в Android решаются отличным от принятого в java способом.  На этапе инсталляции из файла конфигурации читается набор разрешений, которые необходимы приложению для работы, и пользователю, производящему установку, предлагается утвердить их. В дальнейшем при запуске и работе приложения дополнительные запросы не выдаются. При этом приложение может быть подписано ключом из собственного, возможно, само подписанного сертификата разработчика. Требование подписи сертификата &#8220;всем-известным&#8221; центром сертификации отсутствует. Подпись используется только для идентификации разработчика и его приложений и определения, какие приложения могут запускаться в одном процессе и может ли одно приложение вызывать компоненты другого.</p>
<p lang="ru-RU">Загрузка готовых приложений в устройство рекомендуется производить через сайт <em>www.android.com/market</em> .  Однако, это не является обязательным и, сняв (или установив) галочку в соответствующем пункте меню устройства, можно загрузить приложение откуда угодно, например, с компьютера разработчика, используя Wi-Fi. Загрузка приложения, оформленного как файл с расширением apk, производится через браузер устройства, установка после загрузки стартует автоматически. Конечно, при этом, на компьютере, откуда производится загрузка, должен быть запущен http сервер и он должен быть доступен из интернета или локальной сети.</p>
<p lang="ru-RU">Если вы планируете распространять свое приложение через android market, вам обязательно следует изучить, как правильно его лицензировать, но этот вопрос находится за рамками этого обзора (смотри <a href="http://developer.android.com/guide/publishing/licensing.html">http://developer.android.com/guide/publishing/licensing.html</a>).</p>
<p lang="ru-RU">В распоряжении разработчика имеется полный набор инструментов для разработки и отладки приложений.  Если исключить периодические зависания эмулятора, этот набор можно было бы назвать отличным. В разделе <a href="http://developer.android.com/resources/faq/troubleshooting.html">http://developer.android.com/resources/faq/troubleshooting.html</a> имеется ряд советов как справляться с этими зависаниями.</p>
<p lang="ru-RU">В заключение несколько определений и особенностей, которые отличают android приложения от традиционных  java приложений (J2ME, SE, EE).</p>
<p lang="ru-RU">Определения</p>
<p lang="ru-RU">
<p lang="ru-RU">Не запущенное приложение — это инсталлированный на устройстве apk архив (подписанный jar файл). Этот архив включает в себя компоненты приложения — Activity, Service, Broadcast Receiver и Content provider.</p>
<p lang="ru-RU">Запущенное приложение это набор задач. Каждая задача (task) является стеком компонент. Предположим, что Activity_1 (главный экран) вызывает Activity_2 (редактирование параметров), которая в свою очередь Activity_3 (редактирование отдельного параметра). Тогда на момент работы Activity_3 задача будет представлять из себя стек из 3-х компонент. Если Activity_3 завершает работу (вызовом метода finish()), то стек сокращается до 2-х компонент.  Важно, что компонента из одного архива может вызвать компоненту из другого и эта компонента может оказаться в той же задаче. С другой стороны, компонента может вызвать другую компоненту из своего архива и последняя создаст новую задачу. В каждый момент времени только одна задача  имеет на экране активное окно и получает команды от пользователя.</p>
<p lang="ru-RU">Activity можно рассматривать как окно, через которое пользователь взаимодействует с приложением. В отличии от J2ME  оно может занимать не весь экран устройства и это усложняет его жизненный цикл (подробнее смотри документацию). Если Activity становиться неактивным (пользователь переключается на другую задачу и эта Activity целиком(!) закрывается окном другой Activity — см. определение 2), то эта компонента выгружается в первую очередь при нехватке памяти. Нажатие клавиши &#8220;Назад&#8221; завершает текущую Activity и активирует предыдущую компоненту из стека задачи).</p>
<p lang="ru-RU">Service — это фоновый код, который может быть активирован из другой компоненты. Это не нить и не процесс. Просто код в памяти. Этот код выгружается, когда все фоновые Activity уже выгружены. Система прилагает максимальные усилия для сохранения этого кода в памяти.</p>
<p lang="ru-RU">Broadcast Receiver это код, который вызывается в ответ на некоторое внешнее событие, например, низкий заряд батарей устройства. Никакого предположения о его наличии в памяти между обработкой событий не делается.</p>
<p lang="ru-RU">Последний тип компонент  Content provider здесь рассматриваться не будет.</p>
<p lang="ru-RU">
<p lang="ru-RU">
<p lang="ru-RU">Теперь об особенностях.</p>
<p lang="ru-RU">В android нет понятия &#8220;завершить работу приложения&#8221;. Система держит приложение в памяти, пока эта память ей не потребуется, и выгружает его исходя из своих собственных соображений. Метод finish() класса Activity или stopService() класса Service завершают работу компоненты (активити или сервиса), но не завершают приложение. Так, если в этих компонентах были запущены фоновые нити, то они продолжают работу.</p>
<p lang="ru-RU">Для взаимодействия компоненты обмениваются событиями (Intent) и каждая компонента имеет процедуру обработки таких событий. Обработка всех событий происходит в одной нити (если специально не указано другое). Для выполнения долгих задач компонента должна запустить специальную нитку, чтобы не блокировать пользовательский интерфейс. Эта или другая компонента должна позаботиться о корректной остановке этой нити при выгрузке компоненты или приложения.</p>
<p lang="ru-RU">Каждая компонента имеет свой жизненный цикл. Наиболее сложный цикл имеется у Activity. Нужно корректно обрабатывать все изменения статуса компоненты и освобождать или запрашивать вновь нужные ресурсы, а также управлять запущенными нитями.</p>
<p lang="ru-RU">Компонента, получающая управление при запуске android приложения, определяется в файле конфигурации заданием специального фильтра для события запуска. Запуск приложения рассматривается, как обычная обработка события, сгенерированного системой.</p>
<p lang="ru-RU">Если приложению требуется доступ к компонентам устройства, следует указать запрос соответствующих разрешений в файле конфигурации.</p>
<p lang="ru-RU">Может показаться странным, но схемы для проверки содержимого файла шаблона (layout) в природе не существует. Однако адрес<em> http://schemas.android.com/apk/res/android</em>, тем не менее, должен быть указан для префикса android. Я не нашел в сети внятного ответа, почему это так и почему нельзя сгенерировать схему для каждой версии API. С другой стороны, плагин для Eclipse нормально выводит подсказки и подчеркивает ошибки в этих файлах, так что проблем с проверкой этих файлов на этапе набора текста не возникает.</p>
<p lang="ru-RU">
<p lang="ru-RU">
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/02/04/%d0%be%d1%81%d0%be%d0%b1%d0%b5%d0%bd%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d1%8f-java-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

