<?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>Wed, 10 Aug 2011 14:27:42 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<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х дисков. И если бы видео-ролики обладали одинаковой популярностью, то диски являлись бы узким местом при отдаче контента.
Однако же, нам повезло и всегда есть небольшой набор роликов, которые делают 80% [...]]]></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. Установка вручную требует некоторой возни:

Остановить сервер. Убить процесс, если он не остановился сам.
Удалить старые файлы приложения (на всякий случай)
Скопировать новый WAR на сервер. Иногда его [...]]]></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>
		<item>
		<title>Об использовании GWT и GAE в проектах. Часть 2</title>
		<link>http://blog.gramant.ru/2011/01/01/%d0%be%d0%b1-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b8-gwt-%d0%b8-gae-%d0%b2-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%b0%d1%85-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/</link>
		<comments>http://blog.gramant.ru/2011/01/01/%d0%be%d0%b1-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b8-gwt-%d0%b8-gae-%d0%b2-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%b0%d1%85-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/#comments</comments>
		<pubDate>Fri, 31 Dec 2010 23:18:40 +0000</pubDate>
		<dc:creator>Дмитрий Родичев</dc:creator>
				<category><![CDATA[Без категории]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[appengine]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[GWT]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://blog.gramant.ru/?p=748</guid>
		<description><![CDATA[
Вторая часть обзора посвящена среде Google AppEngine.
В отличии от GWT, который является просто средством разработки, одним  из ряда аналогичных, на GAE нужно посмотреть и с другой стороны. GAE  это не просто среда (платформа), где выполняются приложения (кстати,  совсем не обязательно, написанные на GWT), но и хостинг. И помимо  вопроса, насколько GAE [...]]]></description>
			<content:encoded><![CDATA[<div>
<p>Вторая часть обзора посвящена среде Google AppEngine.</p>
<p>В отличии от GWT, который является просто средством разработки, одним  из ряда аналогичных, на GAE нужно посмотреть и с другой стороны. GAE  это не просто среда (платформа), где выполняются приложения (кстати,  совсем не обязательно, написанные на GWT), но и хостинг.<span id="more-748"></span> И помимо  вопроса, насколько GAE отвечает требованиям проекта с точки зрения  разработчика и руководителя проекта – об этом пойдет речь ниже,  возникает вопрос – насколько выгоден этот хостинг, насколько удобно  поддерживать приложение в рабочем состоянии, по сравнению с  традиционными подходами. Я не в курсе всех особенностей, связанных в  вопросом GAE хостинга, но с точки зрения разработчика могу отметить  следующее:<br />
+ возможностей административной консоли (см. ниже) явно не достаточно  для сопровождения и мониторинга больших приложений. Эту функциональность  все равно придется реализовывать и отнюдь не меньшую ее часть.<br />
+ ограничения на ресурсы, api, хранилище данных и др. делают разработку  сложнее, длиннее и дороже, чем для традиционных хостингов.<br />
+ отсутствие необходимости поддерживать работу “железа”, иметь штат  системных админов и других вопросов, связанных с оборудованием и софтом –  это привлекательное обстоятельство для маленьких компаний.<br />
+ будет ли такое решение дешевле в эксплуатации, зависит в первую  очередь от того, какой доход при каком трафике будет приносить  приложение. Возможно, что при небольшом трафике, оно вообще не потребует  дополнительной оплаты хостинга.<br />
+ текущее состояние проекта GAE – предварительный релиз. Это влечет за  собой несколько проблем и вопросов. Во-первых, какие цены на  дополнительные услуги и ресурсы установит google после выпуска  полноценного релиза и будет ли сохранен бесплатный доступ? Во-вторых, в  настоящий момент отсутствует поддержка, кроме как общение с командой  разработчиков через форум, ответы которых на вопросы сообщества не  являются оперативными.<br />
+ большой проблемой является надежность приложений, развернутых на GAE. В  нашей практике были случаи, когда приложение (и не только наше)  становилось недоступным на 30 минут, 1 час или даже 12 часов. Через день  или 2 на форуме появлялись разъяснения от google, что они в это время  проводили обновление ПО или что-то в этом роде. Вопросом  предварительного оповещения о таких ситуациях, по крайней мере для  предварительного релиза, похоже, в google никто особенно не озабочен.  Для коммерческих приложений этот вопрос стоит крайне остро и поэтому в  последнее время целый ряд компаний приняли решение мигрировать свои  приложения с GAE на традиционный хостинг. Вот ссылка на типичный случай  (на английском) &#8211; <a href="http://www.carlosble.com/?p=719" target="_blank">http://www.carlosble.com/?p=719</a></p>
<p>Итак, мой вывод о GAE хостинге таков. GAE может использоваться  небольшими компаниями и индивидуальными предпринимателями для хостинга  проектов, которые не требуют серьезного мониторинга, поддержки и  надежности. Использование хостинга GAE компаниями, имеющими собственный  хостинг и штат нужных специалистов абсолютно неоправданно.</p>
<p>Теперь об особенностях платформы для руководителей проектов и программистов.</p>
<p>+ среда предоставляет разработчику ряд сервисов: сохранение больших  объемов данных – до 2Г, работу с картинками (изменение масштаба и т.п.),  работу с почтой, выборку данных по http, выполнение фоновых задач и др.  Каждый из сервисов навязывает разработчику те или иные ограничения.  Поэтому, если планируется использовать такой сервис, обязательно следует  ознакомиться с актуальной документацией на предмет этих ограничений.  Перечень и API сервисов выглядят более бедными по сравнению с  инфраструктурой, предоставляемой J2EE application server-ом и даже J2SE.  Так, например, отсутствует какая-либо возможность работы с цветом  изображений.</p>
<p>+ GAE накладывает ряд ограничений на использование java классов,  поэтому, планируя архитектуру приложения, следует всегда проверять “JRE  Class White List” на предмет совместимости.</p>
<p>+ крайне болезненным является вопрос использования на GAE различных  framework-ов и библиотек. На сайте  <a href="http://code.google.com/p/googleappengine/wiki/WillItPlayInJava " target="_blank">http://code.google.com/p/googleappengine/wiki/WillItPlayInJava </a>можно  ознакомиться с, как мне показалось, официальным списком поддерживаемых и  не поддерживаемых библиотек. Однако, этот список явно не полный и,  по-видимости, убедиться, что данный framework или библиотека будут  работать в среде GAE можно только загрузив их туда и поработав с ними.  Имеется также официально не поддерживаемая google страница  <a href="http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine" target="_blank">http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine</a>.  Там следует обратить внимание не на ее содержание, а на messages,  размещенные и все еще размещаемые там пользователями и разработчиками.  Возможно, среди них удастся найти информацию о библиотеке, отсутствующей  в официальном списке. Ну и, конечно, не следует забывать про поиск и  конференции.</p>
<p>+ для работы с приложениями, google предоставляет административную  консоль с минимальным набором операций. Большая часть ее экранов – это  отображение статистической и билинговой информации. Сравнивать ее с  традиционными консолями серверов приложений просто бессмысленно. Из  наиболее важных функций следует отметить добавление  пользователей-администраторов (кто может деплоить приложение и имеет  доступ к консоли), управление версиями приложения и фоновыми задачами.  Имеется также не слишком удобный просмотровщик хранилища данных.</p>
<p>+ логирование в целом можно признать удовлетворительным. Логи можно  смотреть через консоль, либо загрузить при помощи утилиты командной  строки на локальный компьютер (что мне, кстати, удавалось не всегда).  Логи периодически уничтожаются, так что, если есть подозрение, что они  вам могут понадобиться – загружайте их, не откладывая на потом.</p>
<p>+ деплой приложения проводится целиком, даже, если во всем приложении  меняется только один файл ресурса. Для деплоя можно использовать  специальную утилиту или делать его при помощи плагина Eclipse-а. Можно  иметь несколько версий одного и того же приложения. Одна версия будет  вызываться по умолчанию, а для других создаются специальные адреса.</p>
<p>+ при деплое google app engine иногда начинает вести себя довольно  странно и выводит (или не выводит) сообщения об ошибках. Здесь  приходится идти в “поиск” и надеяться, что этот вопрос уже решен  сообществом google разработчиков. В моей практике было 2 случая. Первый,  когда после “успешного” деплоя, в логе стали появляться сообщения, о  том, что тот или иной класс не может быть найден. В этом случае я менял  версию приложения, делал повторный деплой, а сбойную версию удалял.  Второй, когда деплой стал время от времени завершаться с сообщением об  ошибках при парсинге web.xml. При этом, иногда десплой проходил успешно,  а иногда – нет. Выяснилось следующее. При создании проекта в eclipse,  web.xml создается для сервлета версии 2.3 с соответствующим определением  DTD. GAE поддерживает версию 2.5 и поэтому в web.xml я использовал  конструкции из 2.5, а заменить DTD на schema для 2.5 – забыл. Ошибка  очевидна, однако, почему деплой с таким кривым web.xml чаще проходил,  чем заканчивался ошибкой, осталось загадкой.</p>
<p>+ вместо домена, предоставляемого google для созданного вами  приложения, можно установить другой домен, владельцем, которого вы  являетесь. Однако, при этом, насколько я понял, вы теряете возможность  работы по Https.</p>
<p>+ встроенная авторизация пользователей крайне ограниченная. Имеется  всего 2 роли – admin (который указывается в админ. консоли) и все  остальные, кто имеет т.н. google account (он  может быть, а может и не  быть адресом вашего почтового ящика на gmail.com). Можно также  предоставить доступ только пользователям с адресами из определенного  домена, зарегистрированного в Google Apps. Если же требуется более  “тонкое” разграничение доступа, придется реализовать его самостоятельно.</p>
<p>+ ключевым элементом большинства приложений является хранилище данных  (datastore). Google предоставляет в распоряжение разработчиков свое  собственное хранилище. Оно не является ни реляционной, ни объектной  базой данный. В частности, там нет понятия foreing key, а перечень  сохраняемых типов ограничен. Крайне важно понимать, что данные вашего  проекта подходят для этого хранилища. Мое мнение состоит в том, что  данные должны быть либо преимущественно “для чтения” и  денормализованными, либо независимыми друг от друга. Даже простые  зависимости между часто модифицируемыми данными крайне плохо подходят  для размещения в GAE datastore и интенсивно расходуют квоту  процессорного времени во время операции модификации.<br />
Имеется несколько способов работы с эти хранилищем.<br />
Во-первых, это – низкоуровневое API. К сожалению, вся документация здесь  ограничивается кратким javadoc. И хотя, по-видимости, это наиболее  эффективный и предсказуемый способ работы, отсутствие документации и  высокая трудоемкость реализации операций (например, создания зависимых  объектов) делают его использование проблематичным.<br />
Во-вторых, это использование JDO или JPA. Этот способ рекомендуется  google, а по JDO есть учебник с примерами. К сожалению, здесь все  обстоит крайне неважно. Реализация API для JDO/JPA создана компанией  DataNucleus. Для работы с конкретным хранилищем данных эта реализация  требует специальный плагин для этого хранилища. Плагин для хранилища  google, написанный программистами google, получился не слишком удачным.  Там имеется множество ограничений и отклонений от спецификаций JDO/JPA,  не описанных ни на сайте google, ни на сайте DataNucleus. Поэтому, если  вы выбираете этот способ работы с хранилищем, будьте готовы к сюрпризам и  дополнительным временным затратам. Из-за такого двойственного характера  этого продукта вам не удастся получить разъяснения ни от одной, ни от  другой компании. В интернете есть много сайтов, посвященных этой теме.  Например, этот  <a href="http://datanucleus.blogspot.com/2010/01/gaej-and-jdojpa.html" target="_blank">http://datanucleus.blogspot.com/2010/01/gaej-and-jdojpa.html</a><br />
В-третьих, как альтернатива двум вышеописанным подходам, сейчас активно  развивается несколько оригинальных продуктов, являющихся “тонкой”  обверткой над низкоуровневым API. Здесь я, к сожалению, не вижу  возможности подробно рассмотреть степень их готовности, качество,  удобство, уровень документированности и т.п.. Это тема для отдельного  поста. Однако, чтобы не быть голословным, приведу ссылки:</p>
<ul>
<li><a href="http://code.google.com/p/simpleds/" target="_blank">http://code.google.com/p/simpleds/</a> ,</li>
</ul>
<ul>
<li><a href="http://code.google.com/p/objectify-appengine/" target="_blank">http://code.google.com/p/objectify-appengine/</a> ,</li>
</ul>
<ul>
<li> <a href="http://www.sienaproject.com/index.html" target="_blank">http://www.sienaproject.com/index.html</a> .</li>
</ul>
<p>+ в заключение насколько допонительных ссылок, описывающих чужой опыт  использования GAE, его ограничения и рекомендуемые подходы к  проектированию ПО (на английском):</p>
<ul>
<li><a href="http://stackoverflow.com/questions/565963/hidden-limitations-of-google-app-engine" target="_blank">http://stackoverflow.com/questions/565963/hidden-limitations-of-google-app-engine</a></li>
</ul>
<ul>
<li><a href="http://paulonjava.blogspot.com/2010/12/tuning-google-appengine.html" target="_blank">http://paulonjava.blogspot.com/2010/12/tuning-google-appengine.html</a></li>
</ul>
<ul>
<li><a href="http://www.reddit.com/r/appengine" target="_blank">http://www.reddit.com/r/appengine</a></li>
</ul>
<p>Надеюсь, изложенное здесь и эти ссылки помогут вам принять верное решение об использовании GAE в вашем проекте.</p></div>
]]></content:encoded>
			<wfw:commentRss>http://blog.gramant.ru/2011/01/01/%d0%be%d0%b1-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b8-gwt-%d0%b8-gae-%d0%b2-%d0%bf%d1%80%d0%be%d0%b5%d0%ba%d1%82%d0%b0%d1%85-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

