<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2russianfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-8483146613266894707</atom:id><lastBuildDate>Thu, 23 Feb 2012 07:16:26 +0000</lastBuildDate><category>грабли</category><category>openid</category><category>postgresql</category><category>дизайн</category><category>качество кода</category><category>useful</category><category>лытдыбр</category><category>apache ant</category><category>аспекты</category><category>skype</category><category>rabbitvcs</category><category>gorm</category><category>dbus</category><category>sybase</category><category>блокировка</category><category>база данных</category><category>grails</category><category>фолксономия</category><category>spring roo</category><category>plugin</category><category>python</category><category>extension</category><category>rss</category><category>производительность</category><category>self-improvement</category><category>вложенная транзакция</category><category>performance</category><category>eclipse</category><category>nfs</category><category>basics</category><category>opera</category><category>транзакция</category><category>linux</category><category>обзор</category><category>extensions</category><category>foreign key</category><category>transaction</category><category>java</category><category>mysql</category><category>разработка</category><category>howto</category><category>best practices</category><category>GAE</category><category>bitly</category><category>constraint</category><category>google chrome</category><category>шопинг</category><category>oracle</category><category>android</category><category>groovy</category><category>книги</category><category>autofs</category><category>delicious</category><category>tiddlywiki</category><category>dropbox</category><category>chromium</category><category>article</category><category>testing</category><category>performance optimization</category><category>концерт</category><category>велосипед</category><category>svn</category><category>nautilus</category><title>Блог о разработке ПО</title><description>Небольшие заметки на айтишные темы.</description><link>http://atamanenko.blogspot.com/</link><managingEditor>noreply@blogger.com (Ginnungagap)</managingEditor><generator>Blogger</generator><openSearch:totalResults>52</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/atamanenko" /><feedburner:info uri="atamanenko" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/atamanenko" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare href="http://lenta.yandex.ru/settings.xml?name=feed&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://lenta.yandex.ru/i/addfeed.gif">?????? ? ??????.?????</feedburner:feedFlare><feedburner:feedFlare href="http://www.plusmo.com/add?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://plusmo.com/res/graphics/fbplusmo.gif">Subscribe with Plusmo</feedburner:feedFlare><feedburner:feedFlare href="http://www.thefreedictionary.com/_/hp/AddRSS.aspx?http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://img.tfd.com/hp/addToTheFreeDictionary.gif">Subscribe with The Free Dictionary</feedburner:feedFlare><feedburner:feedFlare href="http://www.bitty.com/manual/?contenttype=rssfeed&amp;contentvalue=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.bitty.com/img/bittychicklet_91x17.gif">Subscribe with Bitty Browser</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsalloy.com/?rss=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.newsalloy.com/subrss3.gif">Subscribe with NewsAlloy</feedburner:feedFlare><feedburner:feedFlare href="http://www.live.com/?add=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://tkfiles.storage.msn.com/x1piYkpqHC_35nIp1gLE68-wvzLZO8iXl_JMledmJQXP-XTBOLfmQv4zhj4MhcWEJh_GtoBIiAl1Mjh-ndp9k47If7hTaFno0mxW9_i3p_5qQw">Subscribe with Live.com</feedburner:feedFlare><feedburner:feedFlare href="http://mix.excite.eu/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://image.excite.co.uk/mix/addtomix.gif">Subscribe with Excite MIX</feedburner:feedFlare><feedburner:feedFlare href="http://download.attensa.com/app/get_attensa.html?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.attensa.com/blogs/attensa/WindowsLiveWriter/BadgeredintoBadges_10C02/attensa_feed_button5.gif">Subscribe with Attensa for Outlook</feedburner:feedFlare><feedburner:feedFlare href="http://www.webwag.com/wwgthis.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.webwag.com/images/wwgthis.gif">Subscribe with Webwag</feedburner:feedFlare><feedburner:feedFlare href="http://www.podcastready.com/oneclick_bookmark.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.podcastready.com/images/podcastready_button.gif">Subscribe with Podcast Ready</feedburner:feedFlare><feedburner:feedFlare href="http://www.flurry.com/pushRssFeed.do?r=fb&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.flurry.com/images/flurry_rss_logo2.gif">Subscribe with Flurry</feedburner:feedFlare><feedburner:feedFlare href="http://www.wikio.com/subscribe?url=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.wikio.com/shared/img/add2wikio.gif">Subscribe with Wikio</feedburner:feedFlare><feedburner:feedFlare href="http://www.dailyrotation.com/index.php?feed=http%3A%2F%2Ffeeds.feedburner.com%2Fatamanenko" src="http://www.dailyrotation.com/rss-dr2.gif">Subscribe with Daily Rotation</feedburner:feedFlare><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3650030488818989259</guid><pubDate>Wed, 22 Feb 2012 19:17:00 +0000</pubDate><atom:updated>2012-02-23T14:16:26.567+07:00</atom:updated><title>Упрощаем работу с JPA при помощи Spring Data JPA</title><description>&lt;h3&gt;Введение&lt;/h3&gt;
&lt;p&gt;Уже прошло несколько лет с тех пор, как появился JPA. Работа с &lt;a href="http://docs.oracle.com/javaee/5/api/javax/persistence/EntityManager.html"&gt;EntityManager&lt;/a&gt; увлекательна, но разработчики пишут красивый API, а подробности работы с базой данных скрывают. При этом частая проблема - дублирование имплементации, когда из одного DAO в другой у нас плавно перекочёвывает один и тот же код, в лучшем случае этот код переносится в абстрактный базовый DAO. &lt;a href="http://www.springsource.org/spring-data/jpa"&gt;Spring Data&lt;/a&gt;  коренным образом решает проблему - при  его использовании остаётся только API на уровне интерфейсов, вся имплементация создаётся автоматически с использованием &lt;a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming"&gt;AOP&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;История Spring Data&lt;/h3&gt;

Несмотря на то, что проект только недавно достиг версии 1.0, у него достаточно богатая история - раньше он развивался в рамках проекта &lt;a href="http://redmine.synyx.org/projects/show/hades"&gt;Hades&lt;/a&gt;.

&lt;h3&gt;Объявление DAO-интерфейса&lt;/h3&gt;

Итак, для начала нам необходимо объявить DAO-интерфейс, в котором мы будем объявлять методы для работы с сущностью.
&lt;pre class="brush:java"&gt;
public interface UserRepository extends CrudRepository&amp;lt;User, Long&amp;gt; {
}
&lt;/pre&gt;

Данного кода достаточно для обычного DAO с &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt;-методами. 

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;save&lt;/b&gt; - сохраняет или обновляет переданную сущность.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;findOne&lt;/b&gt; - ищет сущность по первичному ключу.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;findAll&lt;/b&gt; - возвращает коллекцию всех сущностей&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;count&lt;/b&gt; - возвращает количество сущностей&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;delete&lt;/b&gt; - удаляет сущность&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;exists&lt;/b&gt; - проверяет, существует ли сущность с данным первичным ключом&lt;/li&gt;
 
&lt;/ul&gt;

Полный список методов, объявленный в CrudRepository можно посмотреть в &lt;a href="http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/CrudRepository.html"&gt;javadoc&lt;/a&gt;.

В случае, если нам нужны не все методы, то есть возможность произвести наследование от интерфейса &lt;a href="http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/repository/Repository.html"&gt;Repository&lt;/a&gt; и перенести в наследника только те методы из интерфейса CrudRepository, которые нужны.

&lt;h3&gt;Поддержка сортировки и постраничного просмотра&lt;/h3&gt;
Очень часто требующаяся функциональность - это возможность возвращать только часть сущностей из БД, например, для реализации постраничного просмотра в пользовательском интерфейсе. Spring Data и тут хорош и предоставляет нам возможность добавить такую функциональность в наш DAO. Для этого достаточно добавить объявление следующего метода в наш DAO-интерфейс:
&lt;pre class="brush:java"&gt;
 Page&amp;lt;User&amp;gt; findAll(Pageable pageable);
&lt;/pre&gt;
Интерфейс &lt;a href="http://static.springsource.org/spring-data/data-commons/docs/current/api/org/springframework/data/domain/Pageable.html"&gt;Pageable&lt;/a&gt; инкапсулирует в себе сведения о номере запрашиваемой страницы, размере страницы, а также требуемой сортировке.

&lt;h3&gt;Ищем данные&lt;/h3&gt;
Как правило, на обычных CRUD-ах DAO не заканчиваются и часто требуются дополнительные методы, которые возвращают только те сущности, которые удовлетворяют заданным условиям. На мой взгляд, Spring Data сильно упрощает жизнь в данной области.

Например, нам нужен методы для поиска пользователя по логину и по его e-mail адресу:

&lt;pre class="brush:java"&gt;
 User findByLogin(String login);
 User findByEmail(String email);
&lt;/pre&gt;

Все просто.

В случае, если нужны более сложные условия для поиска, то и это тоже реализовано.

Spring Data поддерживает следующие операторы:

&lt;ul&gt;
  &lt;li&gt;Between&lt;/li&gt;
  &lt;li&gt;IsNotNull&lt;/li&gt;
  &lt;li&gt;NotNull&lt;/li&gt;
  &lt;li&gt;IsNull&lt;/li&gt;
  &lt;li&gt;Null&lt;/li&gt;
  &lt;li&gt;LessThan&lt;/li&gt;
  &lt;li&gt;LessThanEqual&lt;/li&gt;
  &lt;li&gt;GreaterThan&lt;/li&gt;
  &lt;li&gt;GreaterThanEqual&lt;/li&gt;
  &lt;li&gt;NotLike&lt;/li&gt;
  &lt;li&gt;Like&lt;/li&gt;
  &lt;li&gt;NotIn&lt;/li&gt;
  &lt;li&gt;In&lt;/li&gt;
  &lt;li&gt;Near&lt;/li&gt;
  &lt;li&gt;Within&lt;/li&gt;
  &lt;li&gt;Regex&lt;/li&gt;
  &lt;li&gt;Exists&lt;/li&gt;
  &lt;li&gt;IsTrue&lt;/li&gt;
  &lt;li&gt;True&lt;/li&gt;
  &lt;li&gt;IsFalse&lt;/li&gt;
&lt;li&gt;False&lt;/li&gt;
&lt;li&gt;Not&lt;/li&gt;

&lt;/ul&gt;
Такой внушительный список открывает простор для фантазии, так что можно составить сколь угодно сложный запрос. 

Если необходимо, чтобы в результатах поиска было несколько сущностей, то необходимо называть метод find&lt;b&gt;All&lt;/b&gt;ByBlahBlah

&lt;h3&gt;Поддержка Spring MVC&lt;/h3&gt;
&lt;i&gt;Это часть основана на официальной документации.&lt;/i&gt;
Представьте, что вы разрабатываете веб-приложение с использованием &lt;a href="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html"&gt;Spring MVC&lt;/a&gt;. Тогда вам необходимо будет загружать сущность из базы данных используя параметры HTTP-запроса. Это может выглядеть следующим образом:
&lt;pre class="brush:java"&gt;
@Controller
@RequestMapping("/users")
public class UserController {

  private final UserRepository userRepository;

  public UserController(UserRepository userRepository) {
    userRepository = userRepository;
  }

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {
    
    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user
    // Populate model
    return "user";
  }
}&lt;/pre&gt;

Во-первых, вы объявляете зависимость на DAO, а во-вторых всегда вызываете метод &lt;tt&gt;findOne()&lt;/tt&gt; для загрузки сущности. К счастью, Spring позволяет нам преобразовывать строковые значения из HTTP-запросов в любой нужный тип используя либо &lt;tt&gt;&lt;a href="http://docs.oracle.com/javase/6/docs/api/java/beans/PropertyEditor.html"&gt;PropertyEditor&lt;/a&gt;&lt;/tt&gt;, либо &lt;tt&gt;&lt;a href="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.html"&gt;ConversionService&lt;/a&gt;&lt;/tt&gt;.

Если вы используете Spring версии 3.0 и выше, то вам необходимо добавить следующую конфигурацию:
&lt;pre class="brush:xml"&gt;
&amp;lt;mvc:annotation-driven conversion-service="conversionService" /&amp;gt;
&amp;lt;bean id="conversionService" class="&amp;#8230;.context.support.ConversionServiceFactoryBean"&amp;gt;
  &amp;lt;property name="converters"&amp;gt;
    &amp;lt;list&amp;gt;
      &amp;lt;bean class="org.springframework.data.repository.support.DomainClassConverter"&amp;gt;
        &amp;lt;constructor-arg ref="conversionService" /&amp;gt;
      &amp;lt;/bean&amp;gt;
    &amp;lt;/list&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;

Если же вы используете Spring более старой версии, то вам необходима вот такая конфигурация:

&lt;pre class="brush:xml"&gt;
&amp;lt;bean class="&amp;#8230;.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"&amp;gt;
  &amp;lt;property name="webBindingInitializer"&amp;gt;
    &amp;lt;bean class="&amp;#8230;.web.bind.support.ConfigurableWebBindingInitializer"&amp;gt;
      &amp;lt;property name="propertyEditorRegistrars"&amp;gt;
        &amp;lt;bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" /&amp;gt;
      &amp;lt;/property&amp;gt;
    &amp;lt;/bean&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;

После данных изменений в конфигурации можно переписать контроллер следующим образом:

&lt;pre class="brush:java"&gt;

@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    // Do null check for user
    // Populate model
    return "userForm";
  }
}
&lt;/pre&gt;

Обратите внимание на то, как упростился код и как мы красиво избавились от его дублирования.


&lt;h3&gt;Документация&lt;/h3&gt;
На данный момент документации по проекту не так уж и много, но, тем не менее, она есть:

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/"&gt;Spring Data JPA - Reference Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://static.springsource.org/spring-data/data-commons/docs/current/reference/html/"&gt;Spring Data Commons - Reference Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SpringSource/spring-data-jpa"&gt;исходники на github&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
Spring Data значительно упрощает жизнь при использовании JPA. Рекомендуется к использованию в своих проектах.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3650030488818989259?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=_c-lp87ZLqI:doGHlq8YheU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=_c-lp87ZLqI:doGHlq8YheU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=_c-lp87ZLqI:doGHlq8YheU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=_c-lp87ZLqI:doGHlq8YheU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=_c-lp87ZLqI:doGHlq8YheU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=_c-lp87ZLqI:doGHlq8YheU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=_c-lp87ZLqI:doGHlq8YheU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=_c-lp87ZLqI:doGHlq8YheU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/_c-lp87ZLqI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/_c-lp87ZLqI/jpa-spring-data-jpa.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2012/02/jpa-spring-data-jpa.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3220592373845845617</guid><pubDate>Wed, 08 Feb 2012 18:05:00 +0000</pubDate><atom:updated>2012-02-09T01:05:49.544+07:00</atom:updated><title>Разработка и тестирование Java REST веб-сервисов</title><description>&lt;h4&gt;Введение&lt;/h4&gt;
Для разработки &lt;a href="http://restpatterns.org/"&gt;REST&lt;/a&gt; веб-сервисов Java предлагает &lt;a href="http://jcp.org/en/jsr/detail?id=311"&gt;JSR-311 - JAX-RS: The Java&amp;trade; API for RESTful Web Services&lt;/a&gt;
Как это обычно бывает в мире Enterprise Java, существует несколько реализаций данной спецификации:
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://jersey.java.net/"&gt;Jersey&lt;/a&gt; - это эталонная реализация спецификации от компании &lt;strike&gt;Sun&lt;/strike&gt; Oracle&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cxf.apache.org/"&gt;Apache CXF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.jboss.org/resteasy"&gt;JBoss RESTEasy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
На примере последней реализации, я и расскажу, каким образом можно написать REST-сервис.

&lt;h4&gt;Пишем REST-сервис&lt;/h4&gt;

&lt;pre class="brush:java"&gt;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@Path("/service/entity")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public interface EntityRestService {

 @GET
 @Path("/all")
 EntityList listAll();

 @GET
 @Path("/{id}")
 Entity findById(@PathParam("id") Integer id);


}
&lt;/pre&gt;

Создаём интерфейс, в котором расставляем JAX-RS аннотации. Аннотация &lt;a href="http://jsr311.java.net/nonav/releases/1.1/javax/ws/rs/Path.html"&gt;@Path&lt;/a&gt; указывает путь, по которому будет доступен наш сервис. Аннотация &lt;a href="http://jsr311.java.net/nonav/releases/1.1/javax/ws/rs/GET.html"&gt;@GET&lt;/a&gt; определяет, какой HTTP-запрос будет обрабатываться данным методом. Аннотация &lt;a href="http://jsr311.java.net/nonav/releases/1.1/javax/ws/rs/Produces.html"&gt;@Produces&lt;/a&gt; позволяет указать, в каком формате данный сервис предоставляет результаты.

&lt;h4&gt;Конфигурация JBoss RESTEasy&lt;/h4&gt;

Конфигурация очень проста, во-первых, нам нужно добавить обновить &lt;tt&gt;pom.xml&lt;/tt&gt; и добавить необходимые зависимости:

&lt;h5&gt;Обновляем &lt;tt&gt;pom.xml&lt;/tt&gt;&lt;/h5&gt;

Добавляем compile-time зависимость на JAX-RS API
&lt;pre class="brush:xml"&gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jaxrs-api&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/pre&gt;

и runtime зависимости на реализацию
&lt;pre class="brush:xml"&gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;resteasy-jaxrs&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${resteasy-jaxrs.version}&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;resteasy-jackson-provider&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${resteasy-jaxrs.version}&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;

&lt;/pre&gt;

&lt;h5&gt;Обновляем конфигурацию веб-приложения в &lt;tt&gt;web.xml&lt;/tt&gt;&lt;/h5&gt;
Необходимо выполнить следующую модификацию &lt;tt&gt;web.xml&lt;/tt&gt;
&lt;pre class="brush: xml"&gt;
 &amp;lt;listener&amp;gt;
  &amp;lt;listener-class&amp;gt;org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap&amp;lt;/listener-class&amp;gt;
 &amp;lt;/listener&amp;gt;

 &amp;lt;context-param&amp;gt;
  &amp;lt;param-name&amp;gt;resteasy.servlet.mapping.prefix&amp;lt;/param-name&amp;gt;
  &amp;lt;param-value&amp;gt;/rest&amp;lt;/param-value&amp;gt;
 &amp;lt;/context-param&amp;gt;
 &amp;lt;servlet&amp;gt;
  &amp;lt;servlet-name&amp;gt;REST Easy&amp;lt;/servlet-name&amp;gt;
  &amp;lt;servlet-class&amp;gt;org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher&amp;lt;/servlet-class&amp;gt;
 &amp;lt;/servlet&amp;gt;
 &amp;lt;servlet-mapping&amp;gt;
  &amp;lt;servlet-name&amp;gt;REST Easy&amp;lt;/servlet-name&amp;gt;
  &amp;lt;url-pattern&amp;gt;/rest/*&amp;lt;/url-pattern&amp;gt;
 &amp;lt;/servlet-mapping&amp;gt;
&lt;/pre&gt;

Данный код объявляет &lt;a href="http://docs.jboss.org/resteasy/docs/2.3.1.GA/javadocs/org/jboss/resteasy/plugins/server/servlet/HttpServletDispatcher.html"&gt;сервлет HttpServletDispatcher&lt;/a&gt;, который будет обрабатывать все запросы, которые приходят на &lt;tt&gt;/rest/*&lt;/tt&gt;. Слушатель &lt;tt&gt;&lt;a href="http://docs.jboss.org/resteasy/docs/2.3.1.GA/javadocs/org/jboss/resteasy/plugins/server/servlet/ResteasyBootstrap.html"&gt;ResteasyBootstrap&lt;/a&gt;&lt;/tt&gt; выполняет всю необходимую инициализацию JBoss RESTEasy.


&lt;h5&gt;Поддержка Spring&lt;/h5&gt;
Для того, чтобы использовать &lt;a href="http://www.springsource.org/spring-framework"&gt;Spring Framework&lt;/a&gt;, нам необходимо сделать следующие изменения:

1. Добавить зависимость в &lt;tt&gt;pom.xml&lt;/tt&gt;
&lt;pre class="brush: xml"&gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;resteasy-spring&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${resteasy-jaxrs.version}&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/pre&gt;


2. Добавить Spring-ового слушателя в &lt;tt&gt;web.xml&lt;/tt&gt;

&lt;pre class="brush: xml"&gt;
 &amp;lt;listener&amp;gt;
  &amp;lt;listener-class&amp;gt;org.jboss.resteasy.plugins.spring.SpringContextLoaderListener&amp;lt;/listener-class&amp;gt;
 &amp;lt;/listener&amp;gt;

&lt;/pre&gt;

После этих изменений JBoss RESTEasy будет в курсе про Spring, это позволит в реализации REST-сервисов полноценно использовать все возможности, предоставляемые Spring Framework.

&lt;h4&gt;Пишем тесты&lt;/h4&gt;
Написание тестов - весьма полезная вещь, ниже я покажу пример теста для REST-сервиса.
В рамках данного теста у нас будет подниматься &lt;a href="http://docs.jboss.org/resteasy/docs/2.3.1.GA/javadocs/org/jboss/resteasy/plugins/server/tjws/TJWSEmbeddedJaxrsServer.html"&gt;встраиваемый веб-сервер TJWSEmbeddedJaxrsServer&lt;/a&gt;, к которому мы будем обращаться для тестирования наших REST-сервисов.
&lt;pre class="brush: java"&gt;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext-test.xml"})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
public class TestEntityServiceRest {

 private static final int PORT = 8081;
 private static final String BASE_URL = "http://localhost:" + PORT;

 @Autowired
 EntityService entityService;

 @Autowired
 ConfigurableApplicationContext applicationContext;


 protected HttpClient client;
 protected TJWSEmbeddedJaxrsServer server;


 @Before
 public void setUpClient() throws Exception {
  client = new DefaultHttpClient();
 }

 @Before
 public void setUpServer() throws Exception {
  server = new TJWSEmbeddedJaxrsServer();
  server.setPort(PORT);
  ResteasyDeployment deployment = server.getDeployment();

  server.start();

  Dispatcher dispatcher = deployment.getDispatcher();
  SpringBeanProcessor processor = new SpringBeanProcessor(dispatcher, deployment.getRegistry(), deployment.getProviderFactory());
  applicationContext.addBeanFactoryPostProcessor(processor);

  SpringResourceFactory noDefaults = new SpringResourceFactory(
    "entityServiceRestImpl", applicationContext, EntityRestServiceImpl.class);
  dispatcher.getRegistry().addResourceFactory(noDefaults);
 }

 @After
 public void stop() {
  server.stop();
 }

 @Test
 public void testListAll() throws Exception {

  int i = 0;
  final List&amp;lt;Entity&amp;gt; returnedList = new ArrayList&amp;lt;Entity&amp;gt;();
  int EXPECTED_SIZE = 6;
  while (i &amp;lt; EXPECTED_SIZE) {
   i++;
   final Entity entity = new Entity();
   entity.setId(i);
   entity.setName("test" + i);
   returnedList.add(entity);
  }
  when(entityService.findAll()).thenReturn(returnedList);


  HttpGet get = new HttpGet(BASE_URL + "/service/entity/");

  HttpResponse response = client.execute(get);
  InputStream content = response.getEntity().getContent();

  EntityList result = fromString(EntityList.class, content);
  content.close();

  Assert.assertNotNull(result);
  Assert.assertEquals(EXPECTED_SIZE, result.getEntities().size());
 }

 @Test
 public void testFindById() throws Exception {

  final Entity validEntity = getValidEntity();
  validEntity.setId(2);

  when(entityService.findById(validEntity.getId())).thenAnswer(new Answer&amp;lt;Object&amp;gt;() {
   @Override
   public Object answer(InvocationOnMock invocation) throws Throwable {
    return validEntity;
   }
  });

  HttpGet get = new HttpGet(BASE_URL + "/service/entity/2");

  HttpResponse response = client.execute(get);
  InputStream content = response.getEntity().getContent();

  Entity result = fromString(Entity.class, content);
  content.close();

  Assert.assertNotNull(result);

  deepAssertEquals(validEntity, result);

 }

 private void deepAssertEquals(Object expected, Object actual) throws IllegalAccessException, InvocationTargetException {
  Assert.assertEquals(expected.getClass(), actual.getClass());
  PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(expected.getClass());
  for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
   Object expectedValue = propertyDescriptor.getReadMethod().invoke(expected);
   Object actualValue = propertyDescriptor.getReadMethod().invoke(actual);
   Assert.assertEquals(expectedValue, actualValue);
  }
 }

        public static &amp;lt;T&amp;gt; T fromString(Class&amp;lt;T&amp;gt; clazz, String input) throws JAXBException {
  JAXBContext ctx = JAXBContext.newInstance(clazz);
  Unmarshaller unmarshaller = ctx.createUnmarshaller();
  @SuppressWarnings(&amp;quot;unchecked&amp;quot;)
  T unmarshal = (T) unmarshaller.unmarshal(new StringReader(input));
  return unmarshal;
 }

 public static &amp;lt;T&amp;gt; T fromString(Class&amp;lt;T&amp;gt; clazz, InputStream input) throws JAXBException {
  JAXBContext ctx = JAXBContext.newInstance(clazz);
  @SuppressWarnings(&amp;quot;unchecked&amp;quot;)
  T unmarshal = (T) ctx.createUnmarshaller().unmarshal(input);
  return unmarshal;
 }
}


&lt;/pre&gt;

&lt;h4&gt;Запуск тестов&lt;/h4&gt;
Половина дела сделана, теперь нам необходимо настроить инфраструктуру для тестирования наших свеженаписанных REST-сервисов.

Для этого нам необходимо внести следующие модификации в &lt;tt&gt;pom.xml&lt;/tt&gt;:

&lt;pre class="brush: xml"&gt;

&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;maven-surefire-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.11&amp;lt;/version&amp;gt;

    &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
     &amp;lt;id&amp;gt;Unit tests&amp;lt;/id&amp;gt;
            &amp;lt;phase&amp;gt;test&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
                &amp;lt;goal&amp;gt;test&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
            &amp;lt;configuration&amp;gt;
                &amp;lt;excludes&amp;gt;
                    &amp;lt;exclude&amp;gt;**/IT*.java&amp;lt;/exclude&amp;gt;
                &amp;lt;/excludes&amp;gt;
            &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
        &amp;lt;execution&amp;gt;
            &amp;lt;id&amp;gt;Integration tests&amp;lt;/id&amp;gt;
            &amp;lt;phase&amp;gt;integration-test&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
                &amp;lt;goal&amp;gt;test&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
            &amp;lt;configuration&amp;gt;
                &amp;lt;includes&amp;gt;
                    &amp;lt;include&amp;gt;**/IT*.java&amp;lt;/include&amp;gt;
                &amp;lt;/includes&amp;gt;
            &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
    &amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.httpcomponents&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;httpclient&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;4.1.2&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;resteasy-jaxrs&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;resteasy-spring&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.jboss.resteasy&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;tjws&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${resteasy-jaxrs.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;servlet-api&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;commons-io&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;commons-io&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/pre&gt;

&lt;h4&gt;Заключение&lt;/h4&gt;
В целом, ничего сложного, главное делать всё аккуратно. Хорошего вам кода!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3220592373845845617?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=rEZiO64iSXY:omde14I8tN8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=rEZiO64iSXY:omde14I8tN8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=rEZiO64iSXY:omde14I8tN8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=rEZiO64iSXY:omde14I8tN8:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=rEZiO64iSXY:omde14I8tN8:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=rEZiO64iSXY:omde14I8tN8:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=rEZiO64iSXY:omde14I8tN8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=rEZiO64iSXY:omde14I8tN8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/rEZiO64iSXY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/rEZiO64iSXY/java-rest.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2012/02/java-rest.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-4840394302332756902</guid><pubDate>Fri, 27 Jan 2012 19:43:00 +0000</pubDate><atom:updated>2012-01-28T02:59:27.909+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">аспекты</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><title>Немножко магии от AspectJ</title><description>Наверно, вы уже сталкивались с таким понятием, как AOП - &lt;a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming"&gt;аспектно-ориентированное программирование&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Обычно, про него вспоминают, когда говорят про &lt;a href="http://static.springsource.org/spring/docs/current/reference/html/transaction.html#transaction-declarative-applying-more-than-just-tx-advice"&gt;декларативное использование транзакций&lt;/a&gt;,  про &lt;a href="https://docs.jboss.com/aop/1.0/aspect-library/reference/annotation15_security.html"&gt;проверку прав доступа&lt;/a&gt;, либо про &lt;a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/aop/interceptor/CustomizableTraceInterceptor.html"&gt;реализацию журналирования&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Но это не единственные области применения АОП.&lt;br /&gt;
&lt;br /&gt;
Я хочу показать ещё пару областей применения из реальных проектов:&lt;br /&gt;
&lt;br /&gt;
1. Модификация исходного кода для реализации дополнительных возможностей.&lt;br /&gt;
2. Принудительная проверка контракта между модулями.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Модификация исходного кода для реализации дополнительных возможностей&lt;/h4&gt;Предположим, что у нас есть модуль в приложении, который предоставляет нужную нам функциональность. С модулем всё в порядке, кроме одного - все его методы могут выбрасывать проверяемые исключения, что ведёт к ухудшению читаемости кода, так как вместо простого вызова метода:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;service.doUsefulThing();
&lt;/pre&gt;&lt;br /&gt;
наш вызов превращается в &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;try {
        service.doUsefulThing();
    } catch ( FirstServiceException e) {
        processException(e);
    } catch ( SecondServiceException e) {
        processException(e);
    }
&lt;/pre&gt;&lt;br /&gt;
Дополнительная проблема в том, что у модуля количество модулей 10+, количество методов также велико, что приводит к тому, что блоки &lt;tt&gt;try/catch&lt;/tt&gt; замусоривают код. Решение с использованием паттерна &lt;tt&gt;&lt;a href="https://en.wikipedia.org/wiki/Callback_(computer_programming)"&gt;Callback&lt;/a&gt;&lt;/tt&gt; также приведёт к замусориванию кода.&lt;br /&gt;
&lt;br /&gt;
&lt;h5&gt;Вариант решения проблемы с использованием AOP&lt;/h5&gt;Решение данной проблемы было таким - а что, если используя возможности AOP трансформировать проверяемое исключение в непроверяемое? Таким образом, мы сможем избавиться от скучной проверки на исключения в нашем коде, а для обработки исключения (ведь мы всегда его будем обрабатывать одинаково) достаточно будет использовать обработку исключения на верхнем уровне абстракции.&lt;br /&gt;
&lt;br /&gt;
Для более элегантного решения проблемы было решено добавить собственную аннотацию, которой нужно помечать метод, который использует сервис из "плохого" модуля.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;package com.blogger.atamanenko;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface SuppressExceptions {
}
    
&lt;/pre&gt;&lt;br /&gt;
А также аспект, который бы делал всю нужную нам функциональность:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;public aspect ExceptionSupressingAspect {

        declare soft :ServiceException:  execution(@com.blogger.atamanenko.annotation.SuppressExceptions * *.*(..));

}
&lt;/pre&gt;&lt;br /&gt;
Данный аспект делает в точности следующее: "Смягчает" исключение &lt;tt&gt;ServiceException&lt;/tt&gt; для метода, который помечен аннотацией &lt;tt&gt;@SuppressExceptions&lt;/tt&gt;.&lt;br /&gt;
&lt;br /&gt;
Пример использования:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;@SuppressExceptions
    protected Entity findEntity(final Identifiable id) {
        return entityService.findById(id);
    }
&lt;/pre&gt;&lt;h4&gt;Принудительная проверка контракта между модулями&lt;/h4&gt;Часто нам необходимо принудительно требовать выполнения каких-то архитектурных ограничений, например, контроллеры должны работать только с сервисами и им запрещено напрямую обращаться к базе данных.&lt;br /&gt;
&lt;br /&gt;
Для реализации таких проверок можно также использовать возможности AOP.&lt;br /&gt;
&lt;br /&gt;
Предположим, что в нашем приложении модуль сервиса выставляет наружу &lt;a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html"&gt;DTO&lt;/a&gt; для работы, скрывая при этом классы модели. Для того, чтобы явно запретить доступ к классам и методам модели нам необходимо создать аспект, который бы вызывал ошибку компиляции при нарушении ограничения.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;aspect ForbidAccessToModelAspect {

//      Full prohibition of access to model:
        pointcut accessModel(): call(* com.blogger.atamanenko.app.model..*.*(..));
        declare error: accessModel() : "Illegal call to model";

}
&lt;/pre&gt;&lt;br /&gt;
После объявления такого аспекта, мы получим ошибку компиляции, что, очевидным образом, приведёт к выполнению архитектурного ограничения.&lt;br /&gt;
&lt;br /&gt;
Если же нам необходимо разрешить доступ к одному пакету только из какого-то определённого другого, то мы можем модифицировать наш аспект следующим образом:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java;"&gt;aspect ForbidAccessToModelAspect2 {

        pointcut accessModel(): call(* com.blogger.atamanenko.app.model.**.*(..));

        // Allow access to model from specific package for methods and constructors
        pointcut allowAccessModelFromSpecificPackage(): withincode(* com.blogger.atamanenko.app.allowedpackage..*.*(..));
        pointcut allowAccessModelFromSpecificPackage2(): withincode(com.blogger.atamanenko.app.allowedpackage..*.new(..));

        // forbid usage from any other methods.
        declare error: accessModel() &amp;&amp; !(allowAccessModelFromSpecificPackage() || allowAccessModelFromSpecificPackage()):"Illegal call to Model from forbidden package";

}

&lt;/pre&gt;&lt;br /&gt;
Такой аспект, созданный в нашем модуле запретит нам использовать классы модели из всех пакетов, кроме &lt;tt&gt;com.blogger.atamanenko.app.allowedpackage&lt;/tt&gt;&lt;br /&gt;
&lt;h4&gt;Сборка приложения&lt;/h4&gt;Файл аспекта нужно положить в каталог &lt;tt&gt;src/main/aspect&lt;/tt&gt;, а для сборки приложения необходимо использовать не стандартный Oracle &lt;a href="http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html"&gt;Java Compiler&lt;/a&gt;, а &lt;a href="http://eclipse.org/aspectj/downloads.php"&gt;AspectJ compiler&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Пример конфигурации для &lt;a href="https://maven.apache.org/"&gt;Apache Maven&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml;"&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;aspectj-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${aspectj-maven-plugin.version}&amp;lt;/version&amp;gt;
    &amp;lt;configuration&amp;gt;
        &amp;lt;complianceLevel&amp;gt;1.6&amp;lt;/complianceLevel&amp;gt;
        &amp;lt;aspectLibraries&amp;gt;
            &amp;lt;aspectLibrary&amp;gt;
                &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;spring-aspects&amp;lt;/artifactId&amp;gt;
            &amp;lt;/aspectLibrary&amp;gt;
        &amp;lt;/aspectLibraries&amp;gt;
        &amp;lt;verbose&amp;gt;true&amp;lt;/verbose&amp;gt;
    &amp;lt;/configuration&amp;gt;
    &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
            &amp;lt;phase&amp;gt;process-sources&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
                &amp;lt;goal&amp;gt;compile&amp;lt;/goal&amp;gt;
                &amp;lt;goal&amp;gt;test-compile&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
        &amp;lt;/execution&amp;gt;
    &amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;
&lt;/pre&gt;&lt;h4&gt;Заключение&lt;/h4&gt;Вот в общем-то и всё. Я сознательно не стал описывать языковые конструкции аспектов, так как они подробно описаны в &lt;a href="https://www.eclipse.org/aspectj/doc/next/progguide/index.html"&gt;руководстве AspectJ&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-4840394302332756902?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=Akqe8Sj3NBo:UB-iIvciO0g:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=Akqe8Sj3NBo:UB-iIvciO0g:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=Akqe8Sj3NBo:UB-iIvciO0g:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=Akqe8Sj3NBo:UB-iIvciO0g:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=Akqe8Sj3NBo:UB-iIvciO0g:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=Akqe8Sj3NBo:UB-iIvciO0g:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=Akqe8Sj3NBo:UB-iIvciO0g:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=Akqe8Sj3NBo:UB-iIvciO0g:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/Akqe8Sj3NBo" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/Akqe8Sj3NBo/aspectj.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>9</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2012/01/aspectj.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-4866135366973502728</guid><pubDate>Thu, 21 Jul 2011 05:06:00 +0000</pubDate><atom:updated>2011-07-21T12:07:25.955+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">java</category><category domain="http://www.blogger.com/atom/ns#">basics</category><title>Немного о виртуальных методах в Java</title><description>Сегодня я хочу рассмотреть некоторые особенности переопределения методов в Java.  В java нельзя переопределить:   &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;поля класса&lt;/li&gt;
&lt;li&gt;конструкторы, инициализаторы класса&lt;/li&gt;
&lt;li&gt;статические методы&lt;/li&gt;
&lt;li&gt;статические поля&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Подробнее об этом можно прочитать в &lt;a href="http://java.sun.com/docs/books/jls/third_edition/html/classes.html#228745"&gt;Java Language Specification, §8.4.8&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Итак, в java все нестатические неприватные (то есть, &lt;tt&gt;protected&lt;/tt&gt;, &lt;tt&gt;package&lt;/tt&gt; и &lt;tt&gt;public&lt;/tt&gt;) методы являются виртуальными. Ключевое слово &lt;tt&gt;final&lt;/tt&gt; запрещает возможность дальнейшего переопределения метода в подклассах.  Рассмотрим следующий пример:  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: java"&gt;public class A {
     int i = 3;
     int getI() {return i;}
}

public class B extends A{
     int i = 5;
     int getI() {return i;}
}

A a = new B();
System.out.println(a.i);
System.out.println(a.getI());

&lt;/pre&gt;&lt;br /&gt;
Вопрос: что выведет данный код?&lt;br /&gt;
Ответ: &lt;br /&gt;
1. Так как поля класса не наследуются, то у класса A своё поле i и у класса B тоже своё поле i. Так как для полей полиморфизм не действует, то при обращении a.i мы обращаемся к классу A, поэтому на экран будет выведено 3.&lt;br /&gt;
2. При вызове метода a.getI() у нас в дело вступает полиморфизм, поэтому будет вызван метод от класса, инстанс которого был создан. Соответственно, мы получим на выходе 5.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Другой пример:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: java"&gt;public class A {
     static int i = 3;
     static int getI() {return i;} 
}

public class B extends A{
     static int i = 5;
     static int getI() {return i;}
}

A a = new B();
System.out.println(a.i);
System.out.println(a.getI());

&lt;/pre&gt;&lt;br /&gt;
Статические поля и методы виртуальными не являются, поэтому оба вызова выведут нам 3.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-4866135366973502728?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=bHm0XPjFrWY:J7JYe-iFCo4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=bHm0XPjFrWY:J7JYe-iFCo4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=bHm0XPjFrWY:J7JYe-iFCo4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=bHm0XPjFrWY:J7JYe-iFCo4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=bHm0XPjFrWY:J7JYe-iFCo4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=bHm0XPjFrWY:J7JYe-iFCo4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=bHm0XPjFrWY:J7JYe-iFCo4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=bHm0XPjFrWY:J7JYe-iFCo4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/bHm0XPjFrWY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/bHm0XPjFrWY/java.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2011/07/java.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2061609987772509071</guid><pubDate>Mon, 06 Dec 2010 17:11:00 +0000</pubDate><atom:updated>2012-01-28T19:22:35.277+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">bitly</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><category domain="http://www.blogger.com/atom/ns#">android</category><title>Сокращаем ссылки на андроиде</title><description>&lt;h5&gt;Вступление&lt;/h5&gt;Встала передо мной задача - сокращать ссылки перед тем, как отправлять их в Twitter. Для решения этой задачи я решил использовать &lt;a href="http://bit.ly"&gt;bit.ly&lt;/a&gt;, благо, их API внятный и простой.  &lt;h5&gt;Программируем!&lt;/h5&gt;Решение нарисовалось в виде следующего класса:  &lt;br /&gt;
&lt;pre class="brush: java"&gt;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;


/**
 * Helper class to work with bitly.
 *
 * @author Oleg Atamanenko
 * @since 06-Dec-2010 12:49:36
 */
public class Bitly {

    private static final String TAG = "Bitly";

    private static final String SHORTEN = "/v3/shorten";
    private static final String API_URL = "api.bit.ly";
    private static final String RESPONSE_FORMAT = "json";

    private String username;
    private String apiKey;


    public Bitly(String username, String apiKey) {
        this.username = username;
        this.apiKey = apiKey;
    }

    public String shorten(String longUrl) throws BitlyException {
        DefaultHttpClient httpClient = new DefaultHttpClient();

        try {
            List&lt;NameValuePair&gt; params = new ArrayList&lt;NameValuePair&gt;();
            params.add(new BasicNameValuePair("login", username));
            params.add(new BasicNameValuePair("apiKey", apiKey));
            params.add(new BasicNameValuePair("longUrl", longUrl));
            params.add(new BasicNameValuePair("format", RESPONSE_FORMAT));


            URI uri = URIUtils.createURI("http", API_URL, -1, SHORTEN, URLEncodedUtils.format(params, "UTF-8"), null);
            HttpGet request = new HttpGet(uri);

            Log.d(TAG, "Sending request: " + request.getURI());

            HttpResponse httpResponse = httpClient.execute(request);

            HttpEntity httpEntity = httpResponse.getEntity();
            String response = EntityUtils.toString(httpEntity);
            Log.i(TAG, "Bitly response is: " + response);
            httpResponse.getEntity().consumeContent();

            JSONObject jsonResponse = new JSONObject(response);

            checkForExceptions(jsonResponse);

            JSONObject data = jsonResponse.getJSONObject("data");
            return data.getString("url");

        } catch (ClientProtocolException e) {
            throw new BitlyException(e);
        } catch (IOException e) {
            throw new BitlyException(e);
        } catch (JSONException e) {
            throw new BitlyException(e);
        } catch (URISyntaxException e) {
            throw new BitlyException(e);
        }
    }

    private void checkForExceptions(JSONObject jsonResponse) throws JSONException, BitlyException {
        int statusCode = jsonResponse.getInt("status_code");
        if (statusCode != 200) {
            String message = jsonResponse.getString("status_txt");
            throw new BitlyException(message);
        }
    }

}
&lt;/pre&gt;  Конструктор класс принимает на вход следующие параметры: &lt;ul&gt;&lt;li&gt;&lt;code&gt;userName&lt;/code&gt; - имя пользователя bit.ly.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apiKey&lt;/code&gt; - Ключ для доступа к API, его можно узнать на &lt;a href="https://bit.ly/a/your_api_key"&gt;специальной страничке&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;a href="https://code.google.com/p/bitly-api/wiki/ApiDocumentation"&gt;Полная документация к Bit.ly API&lt;/a&gt; расположена на отдельном проекте в Google Code  &lt;h5&gt;Использование&lt;/h5&gt;Единственный метод, реализованный сейчас - это метод &lt;code&gt;shorten()&lt;/code&gt;. На вход требуется подать полную ссылку &lt;code&gt;longUrl&lt;/code&gt;, на выходе получается укороченная версия ссылки, либо кидается исключение с сообщением от bit.ly API.   &lt;h6&gt;Пример вызова&lt;/h6&gt;&lt;pre class="brush: java"&gt;
Bitly bitly = new Bitly(BITLY_USERNAME, BITLY_API_KEY);
String shortLink = bitly.shorten(link);
&lt;/pre&gt;&lt;br /&gt;
  &lt;h6&gt;Дальнейшие улучшения&lt;/h6&gt;Если перед вами стоит задача быстренько сократить ссылку - то вышеприведённого кода достаточно. Но если вам нужно полноценное решение, со всеми возможностями bit.ly - то посмотрите в сторону &lt;a href="https://code.google.com/p/bitlyj/"&gt;bitlyj&lt;/a&gt;. Правда, я не уверен, что оно взведётся под андроидом.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2061609987772509071?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=o3n3ct1YK7c:7gbHuazO6Fk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=o3n3ct1YK7c:7gbHuazO6Fk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=o3n3ct1YK7c:7gbHuazO6Fk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=o3n3ct1YK7c:7gbHuazO6Fk:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=o3n3ct1YK7c:7gbHuazO6Fk:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=o3n3ct1YK7c:7gbHuazO6Fk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=o3n3ct1YK7c:7gbHuazO6Fk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=o3n3ct1YK7c:7gbHuazO6Fk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/o3n3ct1YK7c" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/o3n3ct1YK7c/blog-post.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/12/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2698098713610160492</guid><pubDate>Fri, 21 May 2010 17:52:00 +0000</pubDate><atom:updated>2010-05-22T00:54:12.134+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">java</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><title>Удаление различных диакритических символов из строки</title><description>&lt;p&gt;Возникла проблема - каким образом заменить в строке символы из национальных кодировок на соответствующие им из латиницы.&lt;/p&gt;&lt;p&gt;Например, из строки explicación получить explicacion.&lt;/p&gt;&lt;pre class="brush: java"&gt;
package com.blogspot.atamanenko;

import java.text.Normalizer;
import java.text.Normalizer.Form;

public class StringNormalizer {

    public static String normalize(String string) {
        return Normalizer.normalize(string, Form.NFD)
            .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
    }
}

&lt;/pre&gt;&lt;p&gt;Вызов &lt;a href='http://java.sun.com/javase/6/docs/api/java/text/Normalizer.html#normalize%28java.lang.CharSequence,%20java.text.Normalizer.Form%29'&gt;Normalizer.normalize&lt;/a&gt; проводит &lt;a href='http://www.unicode.org/reports/tr15/tr15-23.html'&gt;нормализацию&lt;/a&gt; входной строки. Последующий вызов регулярного выражения удаляет все диакритические знаки, полученные после нормализации.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2698098713610160492?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=7VBQZTaxd-g:IEhsA6vdVmY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=7VBQZTaxd-g:IEhsA6vdVmY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=7VBQZTaxd-g:IEhsA6vdVmY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=7VBQZTaxd-g:IEhsA6vdVmY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=7VBQZTaxd-g:IEhsA6vdVmY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=7VBQZTaxd-g:IEhsA6vdVmY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=7VBQZTaxd-g:IEhsA6vdVmY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=7VBQZTaxd-g:IEhsA6vdVmY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/7VBQZTaxd-g" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/7VBQZTaxd-g/blog-post.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/05/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-369491908881359798</guid><pubDate>Fri, 21 May 2010 17:26:00 +0000</pubDate><atom:updated>2010-05-22T00:29:28.088+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">performance</category><category domain="http://www.blogger.com/atom/ns#">testing</category><title>Создание больших объёмов тестовых данных с помощью Databene Benerator</title><description>&lt;p&gt;Периодически необходимо решать задачу создания больших ( и не очень) объёмов тестовых данных для проведения различных видов тестирования - функционального, нагрузочного (тестирование стабильности и производительности). При этом часто получается так, что система на тестовых данных ведёт себя совсем иначе, чем на реальных данных. Причина кроется в том, что создать правдоподобные тестовые данные всегда достаточно сложно.&lt;/p&gt;
&lt;p&gt;Изучая данный вопрос я наткнулся на замечательный фреймворк - &lt;a href="http://databene.org/databene-benerator"&gt;Databene Benerator&lt;/a&gt;, основной целью создания которого как раз и является создание правдоподобных тестовых данных для проведения различных видов тестирования.&lt;/p&gt;
&lt;h4&gt;Установка&lt;/h4&gt;
&lt;p&gt;Установка фреймворка осуществляется двумя способами - как отдельное приложение и как плагин для Maven.&lt;/p&gt;
&lt;h5&gt;Установка под Maven&lt;/h5&gt;
&lt;p&gt;Для использования Databene benerator в проектах, использующих для сборки Apache Maven необходимо добавить в зависимости databene-benerator и сконфигурировать его.&lt;/p&gt;
&lt;pre class="brush: xml;"&gt;
&amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;
         xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd&amp;quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;groupId&amp;gt;com.myorganization&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;databene-benerator-test&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;
  &amp;lt;packaging&amp;gt;jar&amp;lt;/packaging&amp;gt;
  &amp;lt;name&amp;gt;data generation project&amp;lt;/name&amp;gt;
  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.databene&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;databene-benerator&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;0.5.9&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.databene&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;databene-webdecs&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;0.4.9&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.databene&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;databene-commons&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;0.4.9&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.1.6&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
  &amp;lt;build&amp;gt;
    &amp;lt;resources&amp;gt;
      &amp;lt;resource&amp;gt;
        &amp;lt;directory&amp;gt;src/main/resources&amp;lt;/directory&amp;gt;
        &amp;lt;filtering&amp;gt;true&amp;lt;/filtering&amp;gt;
      &amp;lt;/resource&amp;gt;
    &amp;lt;/resources&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;encoding&amp;gt;UTF-8&amp;lt;/encoding&amp;gt;
          &amp;lt;source&amp;gt;1.5&amp;lt;/source&amp;gt;
          &amp;lt;target&amp;gt;1.5&amp;lt;/target&amp;gt;
        &amp;lt;/configuration&amp;gt;
      &amp;lt;/plugin&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.databene&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;maven-benerator-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;0.5.9&amp;lt;/version&amp;gt;
        &amp;lt;executions&amp;gt;
          &amp;lt;execution&amp;gt;
            &amp;lt;phase&amp;gt;compile&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
              &amp;lt;goal&amp;gt;generate&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
          &amp;lt;/execution&amp;gt;
        &amp;lt;/executions&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;descriptor&amp;gt;src/main/resources/benerator.ben.xml&amp;lt;/descriptor&amp;gt;
          &amp;lt;encoding&amp;gt;UTF-8&amp;lt;/encoding&amp;gt;
          &amp;lt;validate&amp;gt;true&amp;lt;/validate&amp;gt;
          &amp;lt;dbUrl&amp;gt;jdbc:mysql://localhost:3306/hrtool?useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;lt;/dbUrl&amp;gt;
          &amp;lt;dbDriver&amp;gt;com.mysql.jdbc.Driver&amp;lt;/dbDriver&amp;gt;
          &amp;lt;dbSchema&amp;gt;database&amp;lt;/dbSchema&amp;gt;
          &amp;lt;dbUser&amp;gt;user&amp;lt;/dbUser&amp;gt;
          &amp;lt;dbPassword&amp;gt;password&amp;lt;/dbPassword&amp;gt;
        &amp;lt;/configuration&amp;gt;
        &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;log4j&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;log4j&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;1.2.13&amp;lt;/version&amp;gt;
            &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
          &amp;lt;/dependency&amp;gt;
          &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.apache.poi&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;poi&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;3.5-beta5&amp;lt;/version&amp;gt;
            &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
          &amp;lt;/dependency&amp;gt;
        &amp;lt;/dependencies&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;
&amp;lt;/project&amp;gt;

&lt;/pre&gt;
&lt;p&gt;В конфигурации необходимо указать как минимум следующие параметры:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;span style=" font-family:'Courier New,courier';"&gt;dbUrl&lt;/span&gt; - строка подключения к базе данных в формате JDBC&lt;/li&gt;
&lt;li&gt;&lt;span style=" font-family:'Courier New,courier';"&gt;dbDriver&lt;/span&gt; - используемый драйвер базы данных&lt;/li&gt;
&lt;li&gt;&lt;span style=" font-family:'Courier New,courier';"&gt;dbSchema&lt;/span&gt; - имя схемы базы данных&lt;/li&gt;
&lt;li&gt;&lt;span style=" font-family:'Courier New,courier';"&gt;dbUser&lt;/span&gt; - имя пользователя, под которым подключаемся к базе данных&lt;/li&gt;
&lt;li&gt;&lt;span style=" font-family:'Courier New,courier';"&gt;dbPassword&lt;/span&gt; - пароль пользователя для подключения к базе данных&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Кроме использования файла pom.xml databene поддерживает возможность указания параметров в файле benerator.properties, что может быть удобно.&lt;/p&gt;
&lt;p&gt;Databene benerator обладает следующими возможностями:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Решение проблемы создания данных в общем виде. На данный момент поддерживаются XML и реляционные базы данных, но не за горами поддержка веб-сервисов, SAP и любых других систем через механизм расширений.&lt;/li&gt;
&lt;li&gt;Юзабилити. Databene-benerator позволяет упростить создание тестовых данных для сложной модели прикладной области.&lt;/li&gt;
&lt;li&gt;Обработка больших объёмов данных.&lt;/li&gt;
&lt;li&gt;Высокая производительность.&lt;/li&gt;
&lt;li&gt;Поддержка доменных областей.&lt;/li&gt;
&lt;li&gt;Качество данных. Фреймворк поддерживает проверку ограничений модели прикладной области.&lt;/li&gt;
&lt;li&gt;Компонентная, легкорасширяемая архитектура.&lt;/li&gt;
&lt;li&gt;Широкие возможности изменения и настройки генерации тестовых данных&lt;/li&gt;
&lt;li&gt;Создание тестовых данных с нуля.&lt;/li&gt;
&lt;li&gt;Импорт и анонимизация реальных данных.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;В комплекте идёт толковая, но, к сожалению, неполная &lt;a href="http://databene.org/download/databene-benerator-manual-0.6.0.pdf"&gt;документация&lt;/a&gt; по возможностям.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-369491908881359798?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=BDTwvM-RC_0:Wky07II5YGo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=BDTwvM-RC_0:Wky07II5YGo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=BDTwvM-RC_0:Wky07II5YGo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=BDTwvM-RC_0:Wky07II5YGo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=BDTwvM-RC_0:Wky07II5YGo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=BDTwvM-RC_0:Wky07II5YGo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=BDTwvM-RC_0:Wky07II5YGo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=BDTwvM-RC_0:Wky07II5YGo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/BDTwvM-RC_0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/BDTwvM-RC_0/databene-benerator.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>2</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/05/databene-benerator.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-690409162698701320</guid><pubDate>Sun, 11 Apr 2010 04:45:00 +0000</pubDate><atom:updated>2010-04-11T11:51:36.120+07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">best practices</category><category domain="http://www.blogger.com/atom/ns#">gorm</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><category domain="http://www.blogger.com/atom/ns#">grails</category><title>Несколько слов о GORM</title><description>&lt;p&gt;В данной заметке хочу поделиться некоторыми моментами использования GORM.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html"&gt;GORM&lt;/a&gt; - это &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt;-фреймворк, используемый в &lt;a href="http://grails.org/"&gt;Grails&lt;/a&gt;. Реализован он поверх &lt;a href="http://hibernate.org/"&gt;Hibernate&lt;/a&gt;, но, при этом, с некоторыми отличными умолчаниями.&lt;/p&gt;
&lt;p&gt;Для разработчиков, знающих &lt;tt&gt;Hibernate&lt;/tt&gt;, рекомендую тщательно изучить &lt;tt&gt;GORM&lt;/tt&gt;, так как его поведение в некоторых случаях отлично от &lt;tt&gt;Hibernate&lt;/tt&gt;, что может приводить к различным сюрпризам.&lt;/p&gt;
&lt;h4&gt;&lt;/h4&gt;
&lt;h4&gt;Маппинг один-ко-многим&lt;/h4&gt;
&lt;p&gt;По умолчанию &lt;tt&gt;GORM&lt;/tt&gt; для связей &lt;em&gt;один ко многим&lt;/em&gt; (one-to-many) &lt;a href="http://www.grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.2.1.2%20One-to-many" title="Grails will, by default, map this kind of relationship with a join table."&gt;создаёт таблицу-связку&lt;/a&gt;, которая обычно нужна только при связях между сущностями вида &lt;em&gt;многие ко многим&lt;/em&gt;. Чтобы исправить это поведение необходимо указать &lt;tt&gt;GORM&lt;/tt&gt;, чтобы он не создавал таблицу связку.&lt;/p&gt;
&lt;pre class="brush: java; highlight: [7]"&gt;
class Person implements Serializable {
  static hasMany = [
    scores: ScoreSheet
  ]
  
  static mapping = {
    scores joinTable: false
  };
}
&lt;/pre&gt;
&lt;h4&gt;Использование однонаправленных связей&lt;/h4&gt;
&lt;p&gt;Если в приложении используются двунаправленные связи и вероятность изменения сущности одновременно несколькими пользователями высокая, то лучше использовать однонаправленные связи для сущностей. Кроме того, лучше проектировать доменные классы таким образом, чтобы связь была не один-ко-многим, а многие к одному.&lt;/p&gt;
&lt;pre class="brush: java"&gt;
class Note implements Serializable {
  static belongsTo = [
    person: Person
  ]
}

class Person implements Serializable {
  // person fields.
}
&lt;/pre&gt;
&lt;p&gt;Для работы с &lt;tt&gt;Notes&lt;/tt&gt; необходимо использовать такие запросы: &lt;/p&gt;
&lt;pre class="brush: java"&gt;
  Note.findByPerson(person).each { -&amp;gt; };
&lt;/pre&gt;
&lt;p&gt; вместо &lt;/p&gt;
&lt;pre class="brush: java"&gt;
  person.notes.each { -&amp;gt; }
&lt;/pre&gt;
&lt;h4&gt;Маппинг иерархии классов доменных сущностей&lt;/h4&gt;
&lt;p&gt;GORM поддерживает только &lt;a href="http://www.grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.2.3%20Inheritance%20in%20GORM"&gt;два варианта &lt;/a&gt;маппинга иерархии классов, в отличии от &lt;a href="http://docs.jboss.org/hibernate/stable/core/reference/en/html/inheritance.html"&gt;Hibernate&lt;/a&gt;: Таблица на всю иерархию (&lt;tt&gt;table-per-hierarchy&lt;/tt&gt;), или таблица на каждый подкласс (&lt;tt&gt;table-per-subclass&lt;/tt&gt;). У маппинга &lt;tt&gt;table-per-hierarchy&lt;/tt&gt; есть серьёзный недостаток - подклассы не могут иметь ненулевые поля. Поэтому, если этот недостаток критичен, то необходимо использовать маппинг &lt;tt&gt;table-per-subclass&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="brush: java; highlight: [7]"&gt;
class Payment {
  Long id
  Long version
  Integer amount

  static mapping = {
    tablePerHierarchy false
  }
}

class CreditCardPayment extends Payment {
  String cardNumber
}
 &lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-690409162698701320?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2r5xMrFT6iA:8rwX1HaStjY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2r5xMrFT6iA:8rwX1HaStjY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2r5xMrFT6iA:8rwX1HaStjY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2r5xMrFT6iA:8rwX1HaStjY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2r5xMrFT6iA:8rwX1HaStjY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2r5xMrFT6iA:8rwX1HaStjY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2r5xMrFT6iA:8rwX1HaStjY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2r5xMrFT6iA:8rwX1HaStjY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/2r5xMrFT6iA" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/2r5xMrFT6iA/gorm.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/04/gorm.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3884458579727001911</guid><pubDate>Mon, 22 Mar 2010 17:50:00 +0000</pubDate><atom:updated>2010-03-23T00:01:36.964+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">skype</category><category domain="http://www.blogger.com/atom/ns#">python</category><category domain="http://www.blogger.com/atom/ns#">dbus</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><title>Общение со Skype через D-Bus на Python</title><description>&lt;p&gt;&lt;b&gt;Summary&lt;/b&gt;: в данной заметке описывается работа с программой Skype через D-Bus на Python.&lt;/p&gt;

&lt;h4&gt;Введение&lt;/h4&gt;
&lt;p&gt;Захотелось мне странного - когда я ухожу домой, мне нужно выключить amarok, kopete и Skype. Собственно, решено было через D-Bus отправлять вышеперечисленным приложениям релевантные сообщения.&lt;/p&gt;

&lt;h4&gt;Используем dbus-send&lt;/h4&gt;
&lt;p&gt;Сначала я использовал обычный dbus-send, что оформилось в виде следующего скрипта &lt;tt&gt;go2home&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="brush: bash"&gt;
#!/bin/sh

# Stop amarok
dbus-send --session --type=method_call --dest=org.kde.amarok /Player org.freedesktop.MediaPlayer.Stop

# Logout from kopete
dbus-send --session --type=method_call --dest=org.kde.kopete /Kopete org.kde.Kopete.disconnectAll 

# Logout from Skype
skypeapi.py 'SET USERSTATUS OFFLINE'

# Lock screen
dbus-send --session --type=method_call --dest=org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.Lock
&lt;/pre&gt;

Детали и параметры работы команды &lt;tt&gt;dbus-send&lt;/tt&gt; описаны в &lt;a href="http://dbus.freedesktop.org/doc/dbus-send.1.html"&gt;man-странице&lt;/a&gt;

&lt;h4&gt;Проблема со скайпом&lt;/h4&gt;
&lt;p&gt; Со скайпом пришлось немного повозиться, так как для работы с ним необходима постоянная сессия, что нельзя сделать с помощью &lt;tt&gt;dbus-send&lt;/tt&gt;. &lt;/p&gt;
&lt;p&gt;Прочитав &lt;a href="https://developer.skype.com/Docs/ApiDoc"&gt;описание протокола&lt;/a&gt; на сайте &lt;/p&gt;
&lt;p&gt; был создан нижеследующий скрипт: skypeapi.py&lt;/p&gt;
&lt;pre class="brush: python"&gt;
#!/usr/bin/env python
import dbus, sys

def main():
    remote_bus = dbus.SessionBus()
    
    # Check if skype is running.
    system_service_list = remote_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus').ListNames()
    skype_api_found = 0
    for service in system_service_list:
        if service=='com.Skype.API':
            skype_api_found = 1
            break
    if not skype_api_found:
        sys.exit('No running API-capable Skype found')

    # Get skype dbus api
    skype_service = remote_bus.get_object('com.Skype.API', '/com/Skype')

    # Connect to skype.
    answer = skype_service.Invoke('NAME SkypeApiClient')
    if answer != 'OK':
        sys.exit('Could not bind to Skype client')

    # Check if protocol is supported.
    answer = skype_service.Invoke('PROTOCOL 1')
    if answer != 'PROTOCOL 1':
        sys.exit('This test program only supports Skype API protocol version 1')

    # Invoke operations
    for arg in sys.argv:
        skype_service.Invoke(arg)
    
    return 0    

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;
&lt;p&gt; При первом запуске скрипта появится скайповский диалог с вопросом, можно ли разрешить приложению доступ к скайпу. После нажатия на &amp;quot;Да&amp;quot; Skype добавит наш скрипт в разрешённые и мы сможем управлять скайпом.&lt;/p&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6ev4yVZkJI/AAAAAAAAA9Q/O87mz2Qnku0/s1600-h/skype.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 376px; height: 185px;" src="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6ev4yVZkJI/AAAAAAAAA9Q/O87mz2Qnku0/s400/skype.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5451519264074338450" /&gt;&lt;/a&gt;

После этого для скрипта &lt;tt&gt;go2home&lt;/tt&gt; был создана иконка на панели.

&lt;h4&gt;Заключение&lt;/h4&gt;
Как видно, работа с DBus из Python проста и элегантна.

В качестве домашнего упражнения предлагаю написать скрипт, который после разблокирования экрана будет запускать все нужные приложения.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3884458579727001911?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4U2nSJB8mjU:6UMHCW-u3JM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4U2nSJB8mjU:6UMHCW-u3JM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4U2nSJB8mjU:6UMHCW-u3JM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4U2nSJB8mjU:6UMHCW-u3JM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4U2nSJB8mjU:6UMHCW-u3JM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4U2nSJB8mjU:6UMHCW-u3JM:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4U2nSJB8mjU:6UMHCW-u3JM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4U2nSJB8mjU:6UMHCW-u3JM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/4U2nSJB8mjU" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/4U2nSJB8mjU/skype-d-bus-python.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6ev4yVZkJI/AAAAAAAAA9Q/O87mz2Qnku0/s72-c/skype.png" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/03/skype-d-bus-python.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-1917754502779751980</guid><pubDate>Sun, 21 Mar 2010 14:54:00 +0000</pubDate><atom:updated>2010-03-27T23:47:25.599+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">howto</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><category domain="http://www.blogger.com/atom/ns#">tiddlywiki</category><title>Разработка макроса для TiddlyWiki</title><description>&lt;p&gt;&lt;b&gt;Summary&lt;/b&gt;: Пример разработки плагина для TiddlyWiki&lt;/p&gt;
&lt;h4&gt;Вступление&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://www.tiddlywiki.com/"&gt;TiddlyWiki&lt;/a&gt; - это вики-движок, полностью написанный на &lt;a href="http://en.wikipedia.org/wiki/JavaScript"&gt;JavaScript&lt;/a&gt; и хранящийся в одном файле (как сам движок, так и содержимое). Создатели позиционируют его как &amp;quot;переиспользуемую нелинейную персональную веб записную книжку&amp;quot;.&lt;/p&gt;
&lt;p&gt;Я давно использую TiddlyWiki в различных целях:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;По прямому назначению.&lt;/li&gt;
&lt;li&gt;Как систему &lt;a href="http://mgsd.tiddlyspot.com/#mGSD"&gt;GTD&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Как домашнюю страницу на компьютере.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Моя домашняя страница на компьютере - это, если говорить терминами TiddlyWiki, &lt;a href="http://en.wikipedia.org/wiki/Tiddler"&gt;тиддлер&lt;/a&gt;, содержащий ссылки на страницы, которые я часто посещаю.&lt;/p&gt;
&lt;p&gt;Для удобства использования я разработал пару стилей CSS, так что ссылки были крупными.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6Y0rP2J0eI/AAAAAAAAA9I/xrZhzZf2ad0/s1600-h/tiddly-before.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 172px; height: 132px;" src="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6Y0rP2J0eI/AAAAAAAAA9I/xrZhzZf2ad0/s400/tiddly-before.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5451102316571251170" /&gt;&lt;/a&gt;
&lt;h4&gt;Исходный код плагина&lt;/h4&gt;
&lt;p&gt;После длительного использования tiddlywiki в качестве домашней страницы я понял, что мне хочется, чтобы с каждой ссылкой была иконка сайта. Вручную вставлять картинки мне не хотелось, поэтому было решено написать собственный плагин.&lt;/p&gt;
&lt;pre class="brush: jscript"&gt;
/*{{{*/
version.extensions.faviconLinkMacro = {major: 0, minor: 1, revision: 0, date: new Date(2010,3,21)};
// Author: Oleg Atamanenko
config.macros.faviconLink = {}
config.macros.faviconLink.handler = function(place, macroName,  params, wikifier, paramString) {
  var linkBox = createTiddlyElement(place, &amp;quot;span&amp;quot;, null, &amp;quot;favIcon&amp;quot;, &amp;quot;&amp;quot;);

  var args = paramString.parseParams(&amp;quot;list&amp;quot;,null,true);
  var link = getParam(args, &amp;quot;link&amp;quot;, 'false');
  
  if (link != 'false'){
    urlParts = link.split('/');
    imgLink = urlParts[0] + &amp;quot;//&amp;quot; + urlParts[2] + &amp;quot;/favicon.ico&amp;quot;;

    var imgElement = createTiddlyElement(linkBox, &amp;quot;img&amp;quot;, null, &amp;quot;faviconImage&amp;quot;, &amp;quot;&amp;quot;);
    imgElement.src = imgLink;
    imgElement.width = 16;
    imgElement.height = 16;

    var linkTitle = getParam(args, &amp;quot;title&amp;quot;, 'false');

    if(linkTitle == 'false'){
      linkTitle = link;
    }
    var linkElement = createTiddlyElement(linkBox, &amp;quot;a&amp;quot;, null, &amp;quot;faviconLink&amp;quot;, linkTitle);
    linkElement.href = link;
    linkElement.target = '_blank';
  }
}

/*}}}*/
&lt;/pre&gt;
&lt;h4&gt; Установка плагина&lt;/h4&gt;
&lt;ol&gt;&lt;li&gt;Создаём новый тиддлер, называем его faviconLinkMacro&lt;/li&gt;
&lt;li&gt;Помечаем тиддлер тегом systemConfig, тогда TiddlyWiki при открытии страницы его подгрузит.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Использование плагина&lt;/h4&gt;
&lt;ol&gt;&lt;li&gt;Плагин создаёт новый макрос, faviconLink, с двумя параметрами link и title.&lt;/li&gt;
&lt;li&gt;Вызов &lt;tt&gt;&amp;lt;&amp;lt;faviconLink link:'https://mail.google.com/mail' title:'mail'&amp;gt;&amp;gt;&lt;/tt&gt; создаёт ссылку с картинкой с GMail&lt;/li&gt;
&lt;/ol&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_y8p0_dtMJ38/S6Y0qnki7nI/AAAAAAAAA9A/Ic3skFoirY8/s1600-h/favicon-sample.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 161px; height: 64px;" src="http://1.bp.blogspot.com/_y8p0_dtMJ38/S6Y0qnki7nI/AAAAAAAAA9A/Ic3skFoirY8/s400/favicon-sample.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5451102305759981170" /&gt;&lt;/a&gt;
&lt;h4&gt;Настройка визульных стилей плагина&lt;/h4&gt;
&lt;p&gt;Макрос создаёт следующие классы:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;span.favIcon&lt;/li&gt;
&lt;li&gt;img.faviconImage&lt;/li&gt;
&lt;li&gt;a.faviconLink&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt; Пример стилей&lt;/h4&gt;
&lt;pre class="brush: css"&gt;
.links span {
  display: block;
  position: relative;
  padding: 0.7em;
  margin: 0.3em;
  min-width: 6em;
  float:left;
  text-align: center;
  font-weight: bold;
  font-size: 1.5em;
  font-family: &amp;quot;Gill Sans MT&amp;quot;, &amp;quot;Candara&amp;quot;, &amp;quot;Arial&amp;quot;; 
  border: 2px solid [[ColorPalette::SecondaryLight]];
  background-color: [[ColorPalette::SecondaryPale]];
}

.links span:hover {
  border: 2px solid [[ColorPalette::SecondaryMid]];
  background-color: [[ColorPalette::SecondaryLight]];
  color: [[ColorPalette::PrimaryMid]];
}

.links img.faviconImage {
  position: relative;
  padding-right: 10px;
}

&lt;/pre&gt;
&lt;p&gt; Данный CSS необходимо добавить в тиддлер с именем StyleSheet&lt;/p&gt;
&lt;h4&gt;Пример тиддлера со ссылками&lt;/h4&gt;
&lt;pre class="brush: html"&gt;
{{links{
&amp;lt;&amp;lt;faviconLink link:'http://delicious.com/dark.schakal/2read' title:'2read'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'https://mail.google.com/mail' title:'mail'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://www.google.com/reader/view' title:'rss'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://atamanenko.blogspot.com/' title:'blog'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://feedburner.google.com/fb/a/myfeeds' title:'feedburner'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'https://www.google.com/analytics/settings/' title:'analytics'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://wave.google.com' title:'wave'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://www.blogger.com/home' title:'blogger'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://twitter.com/' title:'twitter'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'https://www.dropbox.com/home#/' title:'dropbox'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://translate.google.com/toolkit/' title:'translator'&amp;gt;&amp;gt; &amp;lt;&amp;lt;faviconLink link:'http://sibir.megafon.ru/sendsms/' title:'SendSMS'&amp;gt;&amp;gt;
}}}
&lt;/pre&gt;
Вышеприведённый код тиддлера создаёт следующую страницу:

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_y8p0_dtMJ38/S6Y0qOLAO4I/AAAAAAAAA84/DFkORdMO8gE/s1600-h/favicon-sample-big.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 157px;" src="http://1.bp.blogspot.com/_y8p0_dtMJ38/S6Y0qOLAO4I/AAAAAAAAA84/DFkORdMO8gE/s400/favicon-sample-big.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5451102298941963138" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-1917754502779751980?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=kVFT8VuZjc4:m2MIxBoJJqQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=kVFT8VuZjc4:m2MIxBoJJqQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=kVFT8VuZjc4:m2MIxBoJJqQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=kVFT8VuZjc4:m2MIxBoJJqQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=kVFT8VuZjc4:m2MIxBoJJqQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=kVFT8VuZjc4:m2MIxBoJJqQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=kVFT8VuZjc4:m2MIxBoJJqQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=kVFT8VuZjc4:m2MIxBoJJqQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/kVFT8VuZjc4" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/kVFT8VuZjc4/tiddlywiki.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_y8p0_dtMJ38/S6Y0rP2J0eI/AAAAAAAAA9I/xrZhzZf2ad0/s72-c/tiddly-before.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/03/tiddlywiki.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3951746152835182554</guid><pubDate>Tue, 16 Mar 2010 15:52:00 +0000</pubDate><atom:updated>2010-03-16T22:12:48.464+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">производительность</category><category domain="http://www.blogger.com/atom/ns#">self-improvement</category><title>Борьба с убийцами производительности</title><description>&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;: Мой опыт борьбы с &amp;quot;убийцами производительности&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Постановка проблемы&lt;/h3&gt;
&lt;p&gt; Иногда бывает так, что хочется отвлечься от работы, от поставленной задачи на какую-нибудь фигню, лишь бы не заниматься текущей задачей. У меня это чаще всего сводилось к тому, что я начинал читать что-нибудь в сети (&lt;a href="http://www.google.com/reader/view/" target="_blank"&gt;Google Reader&lt;/a&gt;, &lt;a href="http://habrahabr.ru/"&gt;Habrahabr&lt;/a&gt;, &lt;a href="http://www.linux.org.ru/"&gt;LOR&lt;/a&gt;, etc). &lt;/p&gt;
&lt;p&gt;Истоки проблемы могут быть абсолютно разными, например: &lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Долгий запуск среды разработки &lt;/li&gt;
&lt;li&gt;Долгая сборка проекта&lt;/li&gt;
&lt;li&gt;Нудная задача&lt;/li&gt;
&lt;li&gt;Неинтересная задача&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Все эти причины спокойно могут приводить к потере производительности, причём неявным образом, например - &amp;quot;А посижу пока я на хабре, пока полная сборка идёт&amp;quot;, в итоге после 20 минут обнаруживается, что сборка закончилась уже 15 минут назад.&lt;/p&gt;
&lt;p&gt;Так как у меня тайм-киллер вполне простой - интернет, то и решение вполне простое.&lt;/p&gt;
&lt;p&gt;Вот список действий, что я сделал:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Удалил быстрые браузеры, теперь у меня только &lt;a href="http://www.mozilla.com/firefox"&gt;Mozilla Firefox&lt;/a&gt; под Linux и страшный &lt;a href="http://www.microsoft.com/windows/ie/ie6/downloads/default.mspx"&gt;Internet Explorer 6.0 под Windows&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Забанил сайты которые не нужны для работы, но на которых я провожу много времени.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Сначала я сделал радикальный бан - внёс в &lt;tt&gt;/etc/hosts&lt;/tt&gt; эти сайты:&lt;/p&gt;
&lt;pre class="brush: bash"&gt;
127.0.0.1 www.linux.org.ru linux.org.ru
127.0.0.1 www.habrahabr.ru habrahabr.ru
127.0.0.1 www.lorquotes.ru lorquotes.ru
&lt;/pre&gt;
&lt;p&gt; Но потом обнаружилось, что есть ещё &lt;a href="http://www.google.com/reader/view/"&gt;Google Reader&lt;/a&gt;, который также отнимает много времени (у меня было до 200 подписок, что давало до 300 новых статей в сутки). Проблему с &lt;a href="http://www.google.com/reader/view/"&gt;Google Reader&lt;/a&gt; я решал поэтапно: &lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Почистил список лент от юмора, который обновляется практически ежедневно, является пожирателем трафика и времени.&lt;/li&gt;
&lt;li&gt;После этого я почистил список лент от лент, которые достаточно интересные, но при этом без них можно спокойно обходиться (Например, &lt;a href="http://aseigo.blogspot.com/"&gt;блог Аарона Сейго&lt;/a&gt;, одного из главных разработчиков &lt;a href="http://www.kde.org/"&gt;KDE&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Вышеперечисленные действия помогли мне сократить список фидов с 200 сначала до 100 с небольшим, а затем и до 65. Но я понимал, что это ещё не предел и есть к чему стремиться, поэтому решил перейти к использованию оффлайнового &lt;a href="http://www.whatisrss.com/"&gt;RSS&lt;/a&gt;-&lt;a href="http://en.wikipedia.org/wiki/RSS_Reader"&gt;клиента&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Для этого я экспортировал &lt;a href="http://www.opml.org/"&gt;OPML&lt;/a&gt;-файл с подписками из &lt;a href="http://www.google.com/reader/view/"&gt;Google Reader&lt;/a&gt;, удалил все ленты (к сожалению, &lt;a href="http://www.google.com/support/forum/p/reader/thread?tid=53c0b347fa8cd843&amp;hl=en"&gt;удалить аккаунт&lt;/a&gt; на &lt;a href="http://www.google.com/reader/view/"&gt;Google Reader&lt;/a&gt; невозможно, только полностью Google Account) и импортировал на &lt;a href="http://userbase.kde.org/Akregator"&gt;Akregator&lt;/a&gt; на домашней машинке. Это помогло мне аккумулировать все чтения ленты новостей в один временной промежуток - вечером, дома.&lt;/p&gt;
&lt;p&gt;Но, как оказалось, ещё есть к чему стремиться и гугление на просторах сети дало свои результаты: &lt;a href="http://www.proginosko.com/leechblock.html"&gt;&lt;strong&gt;LeechBlock&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;LeechBlock это расширение для Mozilla Firefox, которое позволяет блокировать отдельные сайты на различные промежутки времени.&lt;/p&gt;
&lt;p&gt;Собственно, начав им пользоваться, я понял, что это именно то, что мне нужно:&lt;/p&gt;
&lt;p&gt; &lt;a href="http://picasaweb.google.com/lh/photo/GiOXKIEw3fz0uoNMZxGTlg?authkey=Gv1sRgCOCWw8r8gI-DRQ&amp;feat=embedwebsite"&gt;&lt;img src="http://lh6.ggpht.com/_y8p0_dtMJ38/S56FpVJKVHI/AAAAAAAAA8Q/e6hTfBRPkRo/s800/leechblock.png" title="Leechblock Settings UI" alt="Leechblock Settings UI" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Возможности данного расширения следующие: &lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Блокировка сайтов различные периоды времени&lt;/li&gt;
&lt;li&gt;Блокировка сайтов по временному диапазону, в том числе по дням недели.&lt;/li&gt;
&lt;li&gt;Поддержка нескольких наборов сайтов для блокировки&lt;/li&gt;
&lt;li&gt;Возможность быстрого добавления сайта в список для блокировки &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Конечно, данное расширение не является панацеей, его нужно использовать в комплексе с другими средствами.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3951746152835182554?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=QsGASReJU8g:4cBWyTKeGlU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=QsGASReJU8g:4cBWyTKeGlU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=QsGASReJU8g:4cBWyTKeGlU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=QsGASReJU8g:4cBWyTKeGlU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=QsGASReJU8g:4cBWyTKeGlU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=QsGASReJU8g:4cBWyTKeGlU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=QsGASReJU8g:4cBWyTKeGlU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=QsGASReJU8g:4cBWyTKeGlU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/QsGASReJU8g" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/QsGASReJU8g/blog-post.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh6.ggpht.com/_y8p0_dtMJ38/S56FpVJKVHI/AAAAAAAAA8Q/e6hTfBRPkRo/s72-c/leechblock.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/03/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-4998232370776089357</guid><pubDate>Sat, 06 Feb 2010 17:31:00 +0000</pubDate><atom:updated>2010-02-09T20:50:15.660+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">производительность</category><category domain="http://www.blogger.com/atom/ns#">java</category><category domain="http://www.blogger.com/atom/ns#">performance optimization</category><category domain="http://www.blogger.com/atom/ns#">база данных</category><category domain="http://www.blogger.com/atom/ns#">grails</category><category domain="http://www.blogger.com/atom/ns#">groovy</category><title>Список для проверки при оптимизации Grails приложений</title><description>Выкладываю ниже список задач, которые нужно/можно выполнить для оптимизации приложения, написанного на Grails, может кому пригодится.&lt;br /&gt;
&lt;h4&gt;Тестирование проведённых оптимизаций &lt;/h4&gt;Первым делом необходимо разработать критерии проверки, которые позволят оценить эффективность проведённых оптимизаций.&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Установить &lt;a href="http://code.google.com/p/javamelody/"&gt;Java Melody&lt;/a&gt; &lt;a href="http://www.grails.org/plugin/grails-melody"&gt;плагин&lt;/a&gt; для Grails для проведения анализа. &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Разработать скрипты для проведения нагрузочного тестирования.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Прогнать скрипты.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Проанализировать результаты Java Melody, выявить узкие места, произвести нужные оптимизации.&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Общие оптимизации &lt;/h4&gt;Очень часто обновление до последней версии используемых библиотек попутно улучшает производительность.&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Обновить Java до последней версии&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Обновить Groovy до последней версии.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Обновить Grails до последней версии.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Обновить jQuery до последней версии.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Оптимизировать настройки виртуальной машины Java (например, -server -Xmx270m -Xms270m -XX:MaxPermSize=80m -Xverify:none -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:SurvivorRatio=4 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31 -XX:+AggressiveOpts)&lt;br /&gt;
a. http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html&lt;br /&gt;
Вообще, по оптимизации JVM написаны целые книги, так что не буду здесь останавливаться.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация клиентской части &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Настроить &lt;a href="http://tomcat.apache.org/tomcat-5.5-doc/config/context.html"&gt;кэширование статических ресурсов&lt;/a&gt; в Томкате.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Настроить &lt;a href="http://tomcat.apache.org/tomcat-5.5-doc/config/http.html"&gt;сжатие статических ресурсов&lt;/a&gt; в Томкате.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;(поставить прокси на nginx для кэширования статических ресурсов ?)&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Установить плагины для Firebug, которые оценят производительность клиентской части.&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/speed/page-speed"&gt;PageSpeed&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="http://developer.yahoo.com/yslow/"&gt;YSlow&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Проанализировать страницы веб-сайта с их помощью&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Провести предлагаемые оптимизации.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация клиентской части приложений, написанных на Grails &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Установить плагин &lt;a href="http://www.grails.org/plugin/ui-performance"&gt;UI Performance&lt;/a&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Провести оптимизацию с использованием плагина UI Performance &lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация базы данных &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Оптимизировать производительность сервера базы данных (SQL Server) &lt;br /&gt;
a. http://www.sql-server-performance.com/tips/all_main.aspx &lt;br /&gt;
a. http://msdn.microsoft.com/en-us/library/ms998577.aspx&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Проверить наличие индексов в базе данных, добавить необходимые&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Найти неоптимальные запросы в БД, оптимизировать&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация работы с базой данных &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Обновить версию JDBC-драйвера&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Настроить кэширование в Hibernate.&lt;br /&gt;
Ресурсов в сети предостаточно, навскидку &lt;a href="http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html"&gt;вот&lt;/a&gt; &lt;a href="http://www.devx.com/dbzone/Article/29685/1954"&gt; парочка&lt;/a&gt;.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация серверной части приложения &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Произвести профилирование приложения (использовать Visual VM, YJP Profiler), найти узкие места и оптимизировать их.&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Оптимизация серверной части приложения (опционально) &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Установить плагин &lt;a href="http://www.grails.org/plugin/perf4j"&gt;Perf4J&lt;/a&gt; для грэйлза &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Добавить необходимые счётчики в код&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Прогнать нагрузочные тесты, найти узкие места, оптимизировать.&lt;/li&gt;
&lt;/ol&gt;&lt;h4&gt;Заключение&lt;/h4&gt;В целом, в данном списке нет ничего нового, это всего лишь компиляция публично доступных материалов. Тем не менее, решил это опубликовать, чтобы сэкономить время другим.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-4998232370776089357?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=mJA8ySWqDeQ:nbl2D77LyFo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=mJA8ySWqDeQ:nbl2D77LyFo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=mJA8ySWqDeQ:nbl2D77LyFo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=mJA8ySWqDeQ:nbl2D77LyFo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=mJA8ySWqDeQ:nbl2D77LyFo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=mJA8ySWqDeQ:nbl2D77LyFo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=mJA8ySWqDeQ:nbl2D77LyFo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=mJA8ySWqDeQ:nbl2D77LyFo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/mJA8ySWqDeQ" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/mJA8ySWqDeQ/grails.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>6</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/02/grails.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2319239558066259542</guid><pubDate>Thu, 04 Feb 2010 19:20:00 +0000</pubDate><atom:updated>2010-02-09T13:18:00.017+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">mysql</category><category domain="http://www.blogger.com/atom/ns#">sybase</category><category domain="http://www.blogger.com/atom/ns#">база данных</category><category domain="http://www.blogger.com/atom/ns#">транзакция</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><category domain="http://www.blogger.com/atom/ns#">вложенная транзакция</category><category domain="http://www.blogger.com/atom/ns#">oracle</category><title>Вложенные транзакции в базах данных</title><description>Summary: Некоторые особенности вложенных транзакций.&lt;br /&gt;
&lt;br /&gt;
Иногда бывает так, что при обработке запроса необходимо открыть ещё одну транзакцию в рамках текущей транзакции. Это называется вложенной транзакцией. &lt;br /&gt;
&lt;br /&gt;
Очень многие базы данных не поддерживают вложенные транзакции вообще, например, MySQL и Oracle. А те, что поддерживают, делают это на минимальном уровне, например, Sybase поддерживает только псевдовложенные транзакции.&lt;br /&gt;
&lt;br /&gt;
Вложенные транзакции могут быть следующих видов:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Псевдо-вложенные транзакции&lt;/li&gt;
&lt;li&gt;Вложенная субтранзакция&lt;/li&gt;
&lt;li&gt;Вложенная независимая транзакция&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;h3&gt;Псевдовложенные транзакции&lt;/h3&gt;&lt;br /&gt;
Этот тип транзакций поддерживается базой данных Sybase.&lt;br /&gt;
Псевдо-вложенные транзакции позволяют использовать транзакции одна внутри другой, при этом не реализуя механизм вложенных транзакций. Суть состоит в том, что имеется счётчик уровня вложенности транзакций, который увеличивается на 1 при каждом вызове &lt;tt&gt;BEGIN TRANSACTION&lt;/tt&gt; и уменьшается на 1 при каждом вызове &lt;tt&gt;COMMIT&lt;/tt&gt;. Когда счётчик становится равным 0 происходит коммит всех произведённых изменений.&lt;br /&gt;
&lt;br /&gt;
Если во время проведения транзакции происходит ошибка, то вызывается &lt;tt&gt;ROLLBACK&lt;/tt&gt;, но здесь есть загвоздка - что делать с последующими вызовами &lt;tt&gt;BEGIN TRANSACTION&lt;/tt&gt;  и &lt;tt&gt;COMMIT&lt;/tt&gt;. Оптимальным решением является кидание исключения, но это необходимо отслеживать и учитывать на стадии проектирования и реализации, чтобы не возникло неприятных ситуаций при эксплуатации.&lt;br /&gt;
&lt;br /&gt;
Псевдовложенные транзакции являются самым простым способом реализации вложенных транзакций.&lt;br /&gt;
&lt;br /&gt;
Пример псевдотранзакции для базы данных Sybase:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;begin tran
    select @@trancount
    /* @@trancount = 1 */
    begin tran
        select @@trancount
        /* @@trancount = 2 */
        begin tran
            select @@trancount
            /* @@trancount = 3 */
        commit tran
    commit tran
commit tran
select @@trancount
/* @@ trancount = 0 */
&lt;/pre&gt;&lt;br /&gt;
&lt;h3&gt;Вложенная субтранзакция&lt;/h3&gt;&lt;br /&gt;
Данный вид вложенных транзакций решает проблему с обработкой ошибок при &lt;tt&gt;ROLLBACK&lt;/tt&gt;. &lt;br /&gt;
При коммите вложенной транзакции происходит коммит, но он не обладает свойством &lt;tt&gt;Durability&lt;/tt&gt; - окончательный результат зависит от результата внешней транзакции - если внешняя транзакция заканчивается успешно, то и результат внутренней транзакции также фиксируется. Если же при фиксации изменений внешней транзакции происходит ошибка, то внутренняя транзакция также откатывается. Кроме того, необходимо обратить внимание на то, что если внутренняя транзакция заканчивается неуспешно, то внешняя транзакция может закончиться успешно, если не выкинуть исключение наружу. Таким образом, для данного вида вложенных транзакций необходимо выкидывать исключение наружу, для того, чтобы прервать и откатить внешнюю транзакцию в случае ошибки.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Вложенная независимая транзакция&lt;/h3&gt;Особенность данного вида вложенных транзакций заключается в том, что после создания вложенной транзакции она является полностью независимой от транзакции, внутри которой она была создана. Её результаты фиксируются и откатываются независимо от внешней транзакции.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Вложенные транзакции и принципы ACID&lt;/h3&gt;Вложенные транзакции выглядят достаточно подозрительными в том смысле, что они могут нарушать принципы ACID: &lt;br /&gt;
1. Вложенная субтранзакция может нарушать принцип &lt;tt&gt;Durability&lt;/tt&gt;, так как уже зафиксированные изменения могут откатиться в случае отката внешней транзакции.&lt;br /&gt;
2. Вложенная независимая транзакция может нарушать принципы &lt;tt&gt;Atomicity&lt;/tt&gt; и &lt;tt&gt;Consistency&lt;/tt&gt; при возникновении ошибок во внешней или вложенной транзакции.&lt;br /&gt;
&lt;br /&gt;
Подробнее о принципах &lt;abbr title="Atomicity, Consistency, Isolation, Durability"&gt;ACID&lt;/abbr&gt; можно прочитать в &lt;a href="http://atamanenko.blogspot.com/2009/04/blog-post_24.html"&gt;этом посте&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;Я считаю, что следует избегать ситуаций, когда нужны вложенные транзакции. Кроме того, спецификация Java EE &lt;a href="http://java.sun.com/javaee/6/docs/api/javax/ejb/TransactionAttributeType.html"&gt;не поддерживает&lt;/a&gt; вложенные транзакции.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2319239558066259542?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=t_-_6JIfVv8:TNCqi4AbJ10:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=t_-_6JIfVv8:TNCqi4AbJ10:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=t_-_6JIfVv8:TNCqi4AbJ10:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=t_-_6JIfVv8:TNCqi4AbJ10:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=t_-_6JIfVv8:TNCqi4AbJ10:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=t_-_6JIfVv8:TNCqi4AbJ10:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=t_-_6JIfVv8:TNCqi4AbJ10:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=t_-_6JIfVv8:TNCqi4AbJ10:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/t_-_6JIfVv8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/t_-_6JIfVv8/blog-post.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>9</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2010/02/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3731593876741901400</guid><pubDate>Mon, 14 Dec 2009 17:14:00 +0000</pubDate><atom:updated>2009-12-14T23:17:43.993+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">autofs</category><category domain="http://www.blogger.com/atom/ns#">linux</category><category domain="http://www.blogger.com/atom/ns#">howto</category><category domain="http://www.blogger.com/atom/ns#">nfs</category><title>Настройка autofs для монтирования NFS-ресурсов</title><description>Summary: В данной заметке описана настройка autofs для доступа к сетевым ресурсам, доступным по NFS.&lt;br /&gt;
&lt;br /&gt;
Последние несколько лет (с тех пор, как количество компьютеров дома стало больше одного) возникла проблема беспрепятственного доступа к данным, хранящимся на одном компьютере с другого.&lt;br /&gt;
&lt;br /&gt;
Было найдено самое простое решение - &lt;a href="http://en.wikipedia.org/wiki/Network_File_System_(protocol)"&gt;NFS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Как выяснилось позже это было не самое удачное решение - ноутбук не всегда находился дома, а, значит, домашние сетевые ресурсы не всегда доступны. Соответственно, при загрузке операционной системы происходили задержки из-за поиска компьютера с сетевыми ресурсами. Не очень удобно, но жить можно.&lt;br /&gt;
&lt;br /&gt;
Решил окончательно разобраться с этим и начал искать решение. Оно оказалось на поверхности и затронуло только клиента ресурсов, то есть ноутбук. Решение называется &lt;tt&gt;&lt;a href="http://www.autofs.org/"&gt;autofs&lt;/a&gt;&lt;/tt&gt;.&lt;br /&gt;
&lt;br /&gt;
1. Устанавливаем &lt;tt&gt;autofs&lt;/tt&gt;.&lt;br /&gt;
&lt;pre class="brush: bash"&gt;sudo aptitude install autofs5 nfs-common
&lt;/pre&gt;&lt;br /&gt;
2. Производим настройку.&lt;br /&gt;
Редактируем файл &lt;tt&gt;/etc/auto.master&lt;/tt&gt;. Расскомментируем строку, содержащую строки &lt;tt&gt;/net -hosts&lt;/tt&gt;:&lt;br /&gt;
&lt;pre class="brush: bash"&gt;/net -hosts
+auto.master
&lt;/pre&gt;&lt;br /&gt;
3. В файл &lt;tt&gt;&lt;a href="http://en.wikipedia.org/wiki/Hosts_file"&gt;/etc/hosts&lt;/a&gt;&lt;/tt&gt; можно внести адреса серверов с NFS-ресурсами (для того, чтобы избежать &lt;a href="http://en.wikipedia.org/wiki/Domain_Name_System"&gt;DNS&lt;/a&gt;-запросов). В моём случае:&lt;br /&gt;
&lt;pre class="brush: plain"&gt;192.168.18.1 server
&lt;/pre&gt;&lt;br /&gt;
4. Перезапускаем сервис &lt;tt&gt;autofs&lt;/tt&gt;&lt;br /&gt;
&lt;pre class="brush: plain"&gt;sudo service autofs restart
&lt;/pre&gt;&lt;br /&gt;
5. Теперь открываем в файловом браузер адрес &lt;tt&gt;/net/server&lt;/tt&gt; и видим его сетевые ресурсы, доступные для данного клиента.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_y8p0_dtMJ38/SyZxDikTDxI/AAAAAAAAAvk/LTr3KsGATGk/s1600-h/Screenshot-media+-+administrilo+de+dosieroj.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_y8p0_dtMJ38/SyZxDikTDxI/AAAAAAAAAvk/LTr3KsGATGk/s320/Screenshot-media+-+administrilo+de+dosieroj.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
6. Пользуемся.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3731593876741901400?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=zGQXnv_qaLE:2bD8F1bWd-c:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=zGQXnv_qaLE:2bD8F1bWd-c:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=zGQXnv_qaLE:2bD8F1bWd-c:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=zGQXnv_qaLE:2bD8F1bWd-c:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=zGQXnv_qaLE:2bD8F1bWd-c:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=zGQXnv_qaLE:2bD8F1bWd-c:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=zGQXnv_qaLE:2bD8F1bWd-c:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=zGQXnv_qaLE:2bD8F1bWd-c:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/zGQXnv_qaLE" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/zGQXnv_qaLE/autofs-nfs.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_y8p0_dtMJ38/SyZxDikTDxI/AAAAAAAAAvk/LTr3KsGATGk/s72-c/Screenshot-media+-+administrilo+de+dosieroj.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/autofs-nfs.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-3019070198632525019</guid><pubDate>Tue, 08 Dec 2009 17:43:00 +0000</pubDate><atom:updated>2009-12-08T23:43:24.960+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">delicious</category><category domain="http://www.blogger.com/atom/ns#">chromium</category><category domain="http://www.blogger.com/atom/ns#">google chrome</category><title>Расширение для работы с Delicious доступно на сайте расширений Google Chrome</title><description>Итак, &lt;a href="code.google.com/p/chromium-delicious-extension/"&gt;расширение для работы с Delicious&lt;/a&gt; доступно на &lt;a href="https://chrome.google.com/extensions"&gt;сайте расширений Google Chrome&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="https://chrome.google.com/extensions/detail/anlkjppofaicbdanhhpbbogknfodfhfm/"&gt;Посмотреть и установить&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Новые версии расширения буду закачить на этот сайт.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-3019070198632525019?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=VKLttRWj7Xc:qsfFsOBHJcw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=VKLttRWj7Xc:qsfFsOBHJcw:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=VKLttRWj7Xc:qsfFsOBHJcw:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=VKLttRWj7Xc:qsfFsOBHJcw:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=VKLttRWj7Xc:qsfFsOBHJcw:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=VKLttRWj7Xc:qsfFsOBHJcw:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=VKLttRWj7Xc:qsfFsOBHJcw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=VKLttRWj7Xc:qsfFsOBHJcw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/VKLttRWj7Xc" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/VKLttRWj7Xc/delicious-google-chrome.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/delicious-google-chrome.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-1971197807983104412</guid><pubDate>Tue, 08 Dec 2009 17:17:00 +0000</pubDate><atom:updated>2009-12-08T23:29:02.438+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">useful</category><category domain="http://www.blogger.com/atom/ns#">dropbox</category><title>Использование Dropbox для хранения конфигурационных файлов</title><description>Есть такой замечательный сервис - &lt;a href="https://www.getdropbox.com/"&gt;Dropbox&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
Этот сервис является онлайн-хранилищем ваших данных. Работает очень просто:&lt;br /&gt;
&lt;br /&gt;
1. Вы регистрируетесь в сервисе.&lt;br /&gt;
2. Устанавливаете клиент&lt;br /&gt;
3. Пользуетесь.&lt;br /&gt;
&lt;br /&gt;
Недавно я понял, что этот сервис можно легко и непринуждённо использовать не только для хранения различных документов и фотографий, а также и конфигурационных файлов приложений, что существенно упрощает жизнь, когда у вас несколько рабочих машин (например, у меня домашний компьютер, ноутбук, а также ещё и рабочий) - необходимо каким-то образом синхронизовать конфиги при изменении их на одной из машине с другими.&lt;br /&gt;
&lt;br /&gt;
Итак, что я сделал.&lt;br /&gt;
&lt;br /&gt;
Нижеприведённые действия я делал на одной машине:&lt;br /&gt;
1. Создал каталог &lt;tt&gt;ConfigFiles&lt;/tt&gt;&lt;br /&gt;
2. Переместил в него свои конфигурационные файлы (&lt;tt&gt;~/.zshrc&lt;/tt&gt;, &lt;tt&gt;~/.zsh&lt;/tt&gt;, &lt;tt&gt;~/.vimrc&lt;/tt&gt;, &lt;tt&gt;~/.vim&lt;/tt&gt;)&lt;br /&gt;
3. Из каталога Dropbox сделал соответствующие симлинки в домашнем каталоге.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_y8p0_dtMJ38/Sx6K22h5KKI/AAAAAAAAAuU/DRIi7hO5oFw/s1600-h/Screenshot-Dropbox+-+administrilo+de+dosieroj.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_y8p0_dtMJ38/Sx6K22h5KKI/AAAAAAAAAuU/DRIi7hO5oFw/s320/Screenshot-Dropbox+-+administrilo+de+dosieroj.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
На каждой из машин&lt;br /&gt;
1. Удалил существовавшие конфигурационные файлы&lt;br /&gt;
2. Сделал симлинки файлов из Dropbox на соответствующие конфигурационные файлы.&lt;br /&gt;
&lt;br /&gt;
В случае с &lt;a href="http://www.zsh.org/"&gt;ZSH&lt;/a&gt; (впрочем, для любого другого шелла это тоже реализуется) есть ещё один приятный лайфхак - если на разных машинах должны быть немного разные конфиги (например, другая переменная PATH, CDPATH, то это тоже реализуется просто: &lt;br /&gt;
&lt;br /&gt;
Внесите в свой &lt;tt&gt;~/.zshrc&lt;/tt&gt; следующий код.&lt;br /&gt;
&lt;pre class="brush:bash;"&gt;LOCAL_ENVIRONMENT=~/.environment_`hostname`
if [[ -a $LOCAL_ENVIRONMENT ]]
then
    source $LOCAL_ENVIRONMENT
fi

&lt;/pre&gt;&lt;br /&gt;
Теперь нужно создать в домашнем каталоге файл с локальной конфигурацией:&lt;br /&gt;
&lt;pre class="brush:bash;"&gt;touch ~/.environment_`hostname`

&lt;/pre&gt;&lt;br /&gt;
И внести в него конфигурацию, локальную для машины.&lt;br /&gt;
&lt;br /&gt;
Да, этот файл тоже может быть симлинком на файл из dropbox.&lt;br /&gt;
&lt;br /&gt;
P.S. Если вас заинтересовал сервис, то прошу вас регистироваться по &lt;a href="https://www.dropbox.com/referrals/NTI4MDgzMjc5"&gt;реферальной ссылке&lt;/a&gt;, тогда и вам и мне добавят +250 мегабайт бесплатно.&lt;br /&gt;
&lt;br /&gt;
P.P.S. Да, ещё забыл сказать, что сервис достаточно умный и отправляет дельты файлов во время синхронизации. Подробнее - &lt;a href="https://www.dropbox.com/help/8"&gt;в официальной документации&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-1971197807983104412?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=3ryzDE6E77g:ekcigkV82CA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=3ryzDE6E77g:ekcigkV82CA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=3ryzDE6E77g:ekcigkV82CA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=3ryzDE6E77g:ekcigkV82CA:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=3ryzDE6E77g:ekcigkV82CA:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=3ryzDE6E77g:ekcigkV82CA:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=3ryzDE6E77g:ekcigkV82CA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=3ryzDE6E77g:ekcigkV82CA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/3ryzDE6E77g" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/3ryzDE6E77g/dropbox.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_y8p0_dtMJ38/Sx6K22h5KKI/AAAAAAAAAuU/DRIi7hO5oFw/s72-c/Screenshot-Dropbox+-+administrilo+de+dosieroj.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/dropbox.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-1155013276527510169</guid><pubDate>Mon, 07 Dec 2009 13:25:00 +0000</pubDate><atom:updated>2009-12-07T19:25:49.540+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">rss</category><title>Дайджест ссылок в RSS-ленте</title><description>Решил убрать дайджест ссылок из RSS-ленты.&lt;br /&gt;
&lt;br /&gt;
Те, кому это нужно могут сделать следующее:&lt;br /&gt;
&lt;br /&gt;
1. Добавить в свой RSS-аггрегатор ленту &lt;tt&gt;&lt;a href="http://feeds.delicious.com/v2/rss/dark.schakal?count=15"&gt;http://feeds.delicious.com/v2/rss/dark.schakal?count=15&lt;/a&gt;&lt;/tt&gt;&lt;br /&gt;
2. Если вы используете сервис Delicious, то можете меня добавить меня в свою сеть по ссылке: &lt;a href="http://delicious.com/settings/networkadd?networkadd=dark.schakal"&gt;http://delicious.com/settings/networkadd?networkadd=dark.schakal&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-1155013276527510169?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=9Sd37S6uo-o:HvwWcSi2psc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=9Sd37S6uo-o:HvwWcSi2psc:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=9Sd37S6uo-o:HvwWcSi2psc:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=9Sd37S6uo-o:HvwWcSi2psc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=9Sd37S6uo-o:HvwWcSi2psc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=9Sd37S6uo-o:HvwWcSi2psc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=9Sd37S6uo-o:HvwWcSi2psc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=9Sd37S6uo-o:HvwWcSi2psc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/9Sd37S6uo-o" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/9Sd37S6uo-o/rss_07.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/rss_07.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-8996005673895028731</guid><pubDate>Sat, 05 Dec 2009 19:32:00 +0000</pubDate><atom:updated>2009-12-06T01:32:39.127+06:00</atom:updated><title>RSS лента блога и дайджест ссылок за предыдущий день</title><description>Как вы уже могли заметить, RSS теперь содержит дайджест интересных мне ссылок, которые я нашёл в сети за последнее время.&lt;br /&gt;
&lt;br /&gt;
Отпишитесь в комментариях стоит ли это оставить, или убрать дайджест ссылок из ленты?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-8996005673895028731?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=iMZEQyKNQHI:t-4xN9NPkcU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=iMZEQyKNQHI:t-4xN9NPkcU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=iMZEQyKNQHI:t-4xN9NPkcU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=iMZEQyKNQHI:t-4xN9NPkcU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=iMZEQyKNQHI:t-4xN9NPkcU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=iMZEQyKNQHI:t-4xN9NPkcU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=iMZEQyKNQHI:t-4xN9NPkcU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=iMZEQyKNQHI:t-4xN9NPkcU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/iMZEQyKNQHI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/iMZEQyKNQHI/rss.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>6</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/rss.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-8745875516678173355</guid><pubDate>Sat, 05 Dec 2009 19:03:00 +0000</pubDate><atom:updated>2009-12-06T01:12:14.400+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">rabbitvcs</category><category domain="http://www.blogger.com/atom/ns#">linux</category><category domain="http://www.blogger.com/atom/ns#">svn</category><category domain="http://www.blogger.com/atom/ns#">nautilus</category><title>RabbitVCS - расширение для Nautilus для работы с Subversion</title><description>Раньше я использовал коллекцию скриптов &lt;tt&gt;nautilussvn&lt;/tt&gt;, весьма неудобную в использовании.&lt;br /&gt;
&lt;br /&gt;
Бродя по просторам сети наткнулся на отличную замену. Встречайте - &lt;a href="http://rabbitvcs.org/"&gt;RabbitVCS&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Возможности&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Интеграция с Nautilus&lt;/li&gt;
&lt;li&gt;Поддержка Subversion&lt;/li&gt;
&lt;li&gt;Полная локализация&lt;/li&gt;
&lt;li&gt;Поддержка командной строки&lt;/li&gt;
&lt;li&gt;Доступны пакеты для различных дистрибутивов&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;Установка&lt;/h3&gt;&lt;br /&gt;
&lt;strong&gt;Для Ubuntu Hardy, Intepid, Jaunty&lt;/strong&gt;&lt;br /&gt;
&lt;pre class="brush:bash;"&gt;deb http://ppa.launchpad.net/rabbitvcs/ppa/ubuntu hardy main

&lt;/pre&gt;&lt;br /&gt;
&lt;strong&gt;Для Ubuntu Karmic&lt;/strong&gt;&lt;br /&gt;
&lt;pre class="brush:bash;"&gt;sudo add-apt-repository ppa:rabbitvcs/ppa

&lt;/pre&gt;&lt;br /&gt;
Установка для других дистрибутивов описана &lt;a href="http://wiki.rabbitvcs.org/wiki/download"&gt;на официальном сайте&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Работа&lt;/h3&gt;Несмотря на очень маленький номер версии работает достаточно стабильно, не падает.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Скриншоты&lt;/h4&gt;После установки внешний вид наутилуса преображается и выглядит примерно следующим образом (картинки кликабельны):&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_y8p0_dtMJ38/Sxqtltez5bI/AAAAAAAAAtk/KUPsnbkZTNU/s1600-h/Screenshot-Delicious+-+administrilo+de+dosieroj.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_y8p0_dtMJ38/Sxqtltez5bI/AAAAAAAAAtk/KUPsnbkZTNU/s320/Screenshot-Delicious+-+administrilo+de+dosieroj.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
Диалог коммита выглядит вот так:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_y8p0_dtMJ38/Sxqt9y_wAbI/AAAAAAAAAts/YMfhdkKJ_Ck/s1600-h/Screenshot-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_y8p0_dtMJ38/Sxqt9y_wAbI/AAAAAAAAAts/YMfhdkKJ_Ck/s320/Screenshot-1.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
Контекстное меню:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_y8p0_dtMJ38/SxqwIz8iOfI/AAAAAAAAAt0/bDLo2ZIzpKY/s1600-h/Screenshot-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_y8p0_dtMJ38/SxqwIz8iOfI/AAAAAAAAAt0/bDLo2ZIzpKY/s320/Screenshot-2.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;h3&gt;Заключение&lt;/h3&gt;Достойный аналог TortoiseSVN под линукс.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-8745875516678173355?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=v5_U0B8G5IM:vqpFFxLMQyU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=v5_U0B8G5IM:vqpFFxLMQyU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=v5_U0B8G5IM:vqpFFxLMQyU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=v5_U0B8G5IM:vqpFFxLMQyU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=v5_U0B8G5IM:vqpFFxLMQyU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=v5_U0B8G5IM:vqpFFxLMQyU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=v5_U0B8G5IM:vqpFFxLMQyU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=v5_U0B8G5IM:vqpFFxLMQyU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/v5_U0B8G5IM" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/v5_U0B8G5IM/nautilus-subversion.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_y8p0_dtMJ38/Sxqtltez5bI/AAAAAAAAAtk/KUPsnbkZTNU/s72-c/Screenshot-Delicious+-+administrilo+de+dosieroj.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/12/nautilus-subversion.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2242588892686656946</guid><pubDate>Wed, 25 Nov 2009 17:16:00 +0000</pubDate><atom:updated>2009-11-25T23:40:39.998+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">extensions</category><category domain="http://www.blogger.com/atom/ns#">delicious</category><category domain="http://www.blogger.com/atom/ns#">chromium</category><category domain="http://www.blogger.com/atom/ns#">google chrome</category><title>Расширение Delicious Plugin идёт в массы</title><description>Google запустил сайт &lt;a href="https://chrome.google.com/extensions/" target="_blank"&gt;Chrome Extensions&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
В данный момент сайт доступен только разработчикам расширений. Залил расширение, написал кратенькое описание. Жду запуска сайта. :)&lt;br /&gt;
&lt;br /&gt;
А пока сайт закрыт для рядовых пользователей последнюю версию можно скачать &lt;a href="http://bit.ly/2KYnr8"&gt;тут&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
P.S. А тем временем работа над расширением продолжается.&lt;br /&gt;
&lt;br /&gt;
P.P.S. У расширения появилась своя страничка в сети: &lt;a href="http://code.google.com/p/chromium-delicious-extension/"&gt;Project Home Page&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2242588892686656946?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=lUf6FV0Jefs:8CD0twju0lo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=lUf6FV0Jefs:8CD0twju0lo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=lUf6FV0Jefs:8CD0twju0lo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=lUf6FV0Jefs:8CD0twju0lo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=lUf6FV0Jefs:8CD0twju0lo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=lUf6FV0Jefs:8CD0twju0lo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=lUf6FV0Jefs:8CD0twju0lo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=lUf6FV0Jefs:8CD0twju0lo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/lUf6FV0Jefs" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/lUf6FV0Jefs/delicious-plugin.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>0</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/11/delicious-plugin.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2444095627869846146</guid><pubDate>Tue, 24 Nov 2009 17:09:00 +0000</pubDate><atom:updated>2009-11-26T00:36:09.128+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">extension</category><category domain="http://www.blogger.com/atom/ns#">chromium</category><title>Создание страницы настроек для расширений Google Chrome</title><description>В продолжение &lt;a href="http://atamanenko.blogspot.com/2009/11/delicious-bookmarks-google.html"&gt;предыдущей заметки&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Логично предположить, что у расширений могут быть настройки. В Google Chrome/Chromium для этого есть специальный API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы создать собственную страницу настроек необходимо сделать следующее:&lt;br /&gt;
&lt;br /&gt;
1. Объявить в манифесте страницу настроек&lt;br /&gt;
&lt;pre class="brush:js; highlight: [5]"&gt;{
  "name": "Delicious plugin", 
  "version": "0.2", 
  "background_page": "background.html", 
  "options_page": "options.html"
}
&lt;/pre&gt;&lt;br /&gt;
2. Реализовать страницу с настройками.&lt;br /&gt;
&lt;br /&gt;
Страница с настройками - это обычная HTML-страничка. Для доступа к настройкам Google Chrome предоставляет объект &lt;tt&gt;localStorage&lt;/tt&gt;, который умеет сохранять и возвращать значения.&lt;br /&gt;
&lt;br /&gt;
С объектом &lt;tt&gt;localStorage&lt;/tt&gt; работа идёт как с обычным hash. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:js; html-script: true; highlight:[09, 21, 20, 33, 41]"&gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Delicious Bookmarks Options&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    // Saves options to localStorage.
    function saveOptions() {
        var share = document.getElementById(&amp;quot;share&amp;quot;);
        localStorage[&amp;quot;markPrivate&amp;quot;] = share.checked;

        // Update status to let user know options were saved.
        var status = document.getElementById(&amp;quot;status&amp;quot;);
        status.innerHTML = &amp;quot;Options Saved.&amp;quot;;
        setTimeout(function() {
            status.innerHTML = &amp;quot;&amp;quot;;
        }, 1500);
    }

    // Restores select box state to saved value from localStorage.
    function restoreOptions() {
        var share = localStorage[&amp;quot;markPrivate&amp;quot;];

        if (!share) {
            return;
        }
        var shareCheckbox = document.getElementById(&amp;quot;share&amp;quot;);

        shareCheckbox.checked = share;
        
    }
&amp;lt;/script&amp;gt;

&amp;lt;body onload=&amp;quot;restoreOptions()&amp;quot;&amp;gt;

&amp;lt;label for=&amp;quot;share&amp;quot;&amp;gt;Mark as Private&amp;lt;/label&amp;gt;
&amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;checkbox&amp;quot; name=&amp;quot;share&amp;quot; id=&amp;quot;share&amp;quot; /&amp;gt;


&amp;lt;br&amp;gt;
&amp;lt;button onclick=&amp;quot;saveOptions();&amp;quot;&amp;gt;Save&amp;lt;/button&amp;gt;
&amp;lt;div id=&amp;quot;status&amp;quot;&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
&lt;h2&gt;Чтение настроек&lt;/h2&gt;При инициализации страницы настроек необходимо проставить актуальные значения. Для этого на событе &lt;tt&gt;onload&lt;/tt&gt; навешивается функция &lt;tt&gt;restoreOptions()&lt;/tt&gt;, которая проставляет в пользовательском интерфейсе текущие настройки. &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Запись настроек&lt;/h2&gt;Для сохранения настроек навешивается обработчик &lt;tt&gt;onclick&lt;/tt&gt; для кнопки save - метод &lt;tt&gt;saveOptions&lt;/tt&gt;.&lt;br /&gt;
&lt;br /&gt;
Вот таким простым способом можно сохранять настройки расширения. &lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Документация&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/chrome/extensions/options.html"&gt;Google Chrome Extensions Options&lt;/a&gt;&lt;br /&gt;
&lt;li&gt;&lt;a href="http://www.w3.org/TR/2009/WD-webstorage-20091029/#the-localstorage-attribute"&gt;Документация по localStorage&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2444095627869846146?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4q3JfEDUZOc:rbaXQ0cxWlI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4q3JfEDUZOc:rbaXQ0cxWlI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4q3JfEDUZOc:rbaXQ0cxWlI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4q3JfEDUZOc:rbaXQ0cxWlI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4q3JfEDUZOc:rbaXQ0cxWlI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4q3JfEDUZOc:rbaXQ0cxWlI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=4q3JfEDUZOc:rbaXQ0cxWlI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=4q3JfEDUZOc:rbaXQ0cxWlI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/4q3JfEDUZOc" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/4q3JfEDUZOc/google-chrome.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>1</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/11/google-chrome.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-5651858402317760145</guid><pubDate>Sat, 14 Nov 2009 16:34:00 +0000</pubDate><atom:updated>2009-11-24T23:07:17.936+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">extension</category><category domain="http://www.blogger.com/atom/ns#">delicious</category><category domain="http://www.blogger.com/atom/ns#">howto</category><category domain="http://www.blogger.com/atom/ns#">chromium</category><category domain="http://www.blogger.com/atom/ns#">article</category><title>Расширение Delicious Bookmarks для Google Chrome/Chromium</title><description>&lt;h1&gt;Введение&lt;/h1&gt;&lt;br /&gt;
Решил научиться писать собственные расширения для &lt;a href="http://www.google.com/chrome"&gt;Google Chrome&lt;/a&gt;/&lt;a href="http://www.chromium.org/"&gt;Chromium&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
За идею взял официальное расширение от &lt;a href="http://m.www.yahoo.com/"&gt;Yahoo!&lt;/a&gt; для &lt;a href="http://www.mozilla.com/en-US/firefox/firefox.html"&gt;Firefox&lt;/a&gt; - &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/3615"&gt;Delicious Bookmarks&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Структура расширения&lt;/h1&gt;&lt;br /&gt;
Расширение - файл с расширением &lt;tt&gt;.crx&lt;/tt&gt;. На самом деле это просто &lt;tt&gt;ZIP&lt;/tt&gt;-архив с файлом манифеста внутри.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Файл манифеста&lt;/h1&gt;&lt;pre class="brush:js; highlight: [2, 3, 4, 5, 10, 16, 23]"&gt;{
"name": "Delicious plugin", // 1
"version": "0.2", // 2
"background_page": "background.html", // 3
"permissions": [ // 4
"bookmarks",
"tabs"
],

"browser_action": { // 5
"name": "Save bookmark to delicious.com",
"default_title": "Save bookmark to delicious.com",
"default_icon": "delicious.20.gif" // optional
},

"content_scripts": [ // 6
{
"matches": ["http://*/*", "https://*/*"],
"js": ["getDocumentSelection.js"]
}
],

"options_page": "options.html" // 7
}
&lt;/pre&gt;&lt;br /&gt;
Манифест - файл в формате &lt;a href="http://json.org/"&gt;JSON&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Манифест состоит из следующих частей:&lt;br /&gt;
&lt;br /&gt;
1. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;name&lt;/tt&gt;&lt;/span&gt; - Имя расширения&lt;br /&gt;
2. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;version&lt;/tt&gt;&lt;/span&gt; - Версия расширения&lt;br /&gt;
3. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;background_page&lt;/tt&gt;&lt;/span&gt; - основной файл с расширением. Это обычный HTML-файл с разметкой.&lt;br /&gt;
4. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;permissions&lt;/tt&gt;&lt;/span&gt; - разрешения для расширения. Указываем, что нам нужен доступ к вкладкам (tabs) и закладкам (bookmarks).&lt;br /&gt;
5. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;browser_action&lt;/tt&gt;&lt;/span&gt; - указываем, что нужно отобразить на панели инструментов браузера.&lt;br /&gt;
6. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;content_scripts&lt;/tt&gt;&lt;/span&gt; - Для доступа к DOM отображаемой страницы необходимы content scripts, данный блок регистрирует скрипт &lt;tt&gt;getDocumentSelection.js&lt;/tt&gt; для всех &lt;tt&gt;http&lt;/tt&gt; и &lt;tt&gt;https&lt;/tt&gt; страниц.&lt;br /&gt;
7. &lt;span style="font-weight:bold;"&gt;&lt;tt&gt;options_page&lt;/tt&gt;&lt;/span&gt; - страница с настройками.&lt;br /&gt;
&lt;br /&gt;
Подробное описание файла манифеста есть &lt;a href="http://code.google.com/chrome/extensions/manifest.html"&gt;на официальном сайте&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Описание работы расширения&lt;/h1&gt;&lt;br /&gt;
&lt;br /&gt;
Расширение работает следующим образом:&lt;br /&gt;
1. При клике на значок расширения вызывается обработчик &lt;tt&gt;addBookmark&lt;/tt&gt;&lt;br /&gt;
2. Обработчик открывает коммуникационный порт и отправляет сообщение скрипту содержимого.&lt;br /&gt;
3. Скрипт содержимого забирает выделенный текст и отправляет назад расширению выделенный текст.&lt;br /&gt;
4. Расширение забирает выделенный текст, определяет настройки сохранения (Private/Public) и вызывает окно сохранения &lt;span style="font-style:italic;"&gt;Delicious&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Реализация расширения&lt;/h1&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight:bold;"&gt;background.html&lt;/span&gt;:&lt;br /&gt;
&lt;pre class="brush:js; html-script: true"&gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;script&amp;nbsp;type=&amp;quot;text/javascript&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;function&amp;nbsp;saveBookmark()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Send&amp;nbsp;our&amp;nbsp;password&amp;nbsp;to&amp;nbsp;the&amp;nbsp;current&amp;nbsp;tab&amp;nbsp;when&amp;nbsp;clicked.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chrome.tabs.getSelected(null,&amp;nbsp;function(tab)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;port&amp;nbsp;=&amp;nbsp;chrome.tabs.connect(tab.id,&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name&amp;nbsp;:&amp;nbsp;&amp;quot;deliciousBookmark&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port.postMessage(&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;action&amp;nbsp;:&amp;nbsp;'getSelection'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;function&amp;nbsp;addBookmark(id,&amp;nbsp;bookmark)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(&amp;quot;added&amp;nbsp;bookmark:&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;+&amp;nbsp;bookmark);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;saveBookmark();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chrome.bookmarks.onCreated.addListener(addBookmark);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(&amp;quot;Registered&amp;nbsp;listener&amp;quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;function&amp;nbsp;getShareStatus()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;markPrivate&amp;nbsp;=&amp;nbsp;localStorage[&amp;quot;markPrivate&amp;quot;];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;share&amp;nbsp;=&amp;nbsp;&amp;quot;yes&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(markPrivate&amp;nbsp;==&amp;nbsp;&amp;quot;true&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;share&amp;nbsp;=&amp;nbsp;&amp;quot;no&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;share;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chrome.extension.onConnect.addListener(function(port)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.assert(port.name&amp;nbsp;==&amp;nbsp;&amp;quot;deliciousBookmark&amp;quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port.onMessage.addListener(function(msg)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;selection&amp;nbsp;=&amp;nbsp;msg.selection;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chrome.tabs.getSelected(null,&amp;nbsp;function(tab)&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;url&amp;nbsp;=&amp;nbsp;encodeURIComponent(tab.url);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!url&amp;nbsp;||&amp;nbsp;url&amp;nbsp;===&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;title&amp;nbsp;=&amp;nbsp;encodeURIComponent(tab.title);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;description&amp;nbsp;=&amp;nbsp;encodeURIComponent(selection);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;share&amp;nbsp;=&amp;nbsp;getShareStatus();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;f&amp;nbsp;=&amp;nbsp;'http://delicious.com/save?url='&amp;nbsp;+&amp;nbsp;url&amp;nbsp;+&amp;nbsp;'&amp;amp;title='&amp;nbsp;+&amp;nbsp;title&amp;nbsp;+&amp;nbsp;'&amp;amp;notes='&amp;nbsp;+&amp;nbsp;description
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;'&amp;amp;share='&amp;nbsp;+&amp;nbsp;share&amp;nbsp;+&amp;nbsp;'&amp;amp;v=5&amp;amp;';
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window.open(f&amp;nbsp;+&amp;nbsp;'noui=1&amp;amp;jump=doclose',&amp;nbsp;'deliciousuiv5',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'location=yes,links=no,scrollbars=no,toolbar=no,width=550,height=550');

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chrome.browserAction.onClicked.addListener(saveBookmark);
&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight:bold;"&gt;getDocumentSelection.js&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:js"&gt;var port = chrome.extension.connect( {
name : "deliciousBookmark"
});

// Also listen for new channels from the extension for when the button is
// pressed.
chrome.extension.onConnect.addListener(function(port) {
console.assert(port.name == "deliciousBookmark");
port.onMessage.addListener(function(msg) {
if (msg.action == 'getSelection') {
var responsePort = chrome.extension.connect( {
name : "deliciousBookmark"
});
var description = document.getSelection() ? '' + document.getSelection() : '';
responsePort.postMessage( {
selection : description
});
}
});
});


&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight:bold;"&gt;options.html&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:js; html-script: true"&gt;&amp;lt;html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Delicious Bookmarks Options&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    // Saves options to localStorage.
    function saveOptions() {
        var share = document.getElementById(&amp;quot;share&amp;quot;);
        localStorage[&amp;quot;markPrivate&amp;quot;] = share.checked;

        // Update status to let user know options were saved.
        var status = document.getElementById(&amp;quot;status&amp;quot;);
        status.innerHTML = &amp;quot;Options Saved.&amp;quot;;
        setTimeout(function() {
            status.innerHTML = &amp;quot;&amp;quot;;
        }, 1500);
    }

    // Restores select box state to saved value from localStorage.
    function restoreOptions() {
        var share = localStorage[&amp;quot;markPrivate&amp;quot;];

        if (!share) {
            return;
        }
        var shareCheckbox = document.getElementById(&amp;quot;share&amp;quot;);

        shareCheckbox.checked = share;
        
    }
&amp;lt;/script&amp;gt;

&amp;lt;body onload=&amp;quot;restoreOptions()&amp;quot;&amp;gt;

&amp;lt;label for=&amp;quot;share&amp;quot;&amp;gt;Mark as Private&amp;lt;/label&amp;gt;
&amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;checkbox&amp;quot; name=&amp;quot;share&amp;quot; id=&amp;quot;share&amp;quot; /&amp;gt;


&amp;lt;br&amp;gt;
&amp;lt;button onclick=&amp;quot;saveOptions();&amp;quot;&amp;gt;Save&amp;lt;/button&amp;gt;
&amp;lt;div id=&amp;quot;status&amp;quot;&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Сборка расширения&lt;/h1&gt;&lt;br /&gt;
В Chromium есть возможность собрать расширение.&lt;br /&gt;
1. Открываем адрес &lt;tt&gt;chrome://extensions/&lt;/tt&gt;&lt;br /&gt;
2. В правом верхнем углу нажимаем Developer Mode.&lt;br /&gt;
3. На появившейся панели инструментов кликаем Pack Extension&lt;br /&gt;
4. Выбираем каталог, в котором находится расширение&lt;br /&gt;
5. Chromium автоматически собирает расширение в каталоге с исходниками расширения.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;&lt;br /&gt;
Писать расширения для Google Chrome не просто, а очень просто. &lt;br /&gt;
&lt;br /&gt;
Готовое и собранное расширение можно &lt;a href="http://bit.ly/2KYnr8"&gt;скачать здесь&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Дополнительная информация&lt;/h1&gt;&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;a href="http://code.google.com/chrome/extensions/devguide.html"&gt;Google Chrome Extensions Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/chrome/extensions/manifest.html"&gt;Manifest file description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/chrome/extensions/api_index.html"&gt;Chrome Extensions API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/chrome/extensions/packaging.html"&gt;Extension Packaging&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;br /&gt;
В следующей заметке опишу работу messaging для коммуникации расширения со скриптами содержимого.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-5651858402317760145?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=SvwGt8RGqec:CFoBFQNDNNc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=SvwGt8RGqec:CFoBFQNDNNc:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=SvwGt8RGqec:CFoBFQNDNNc:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=SvwGt8RGqec:CFoBFQNDNNc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=SvwGt8RGqec:CFoBFQNDNNc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=SvwGt8RGqec:CFoBFQNDNNc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=SvwGt8RGqec:CFoBFQNDNNc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=SvwGt8RGqec:CFoBFQNDNNc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/SvwGt8RGqec" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/SvwGt8RGqec/delicious-bookmarks-google.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>9</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/11/delicious-bookmarks-google.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-8421063008720169663</guid><pubDate>Mon, 02 Nov 2009 17:26:00 +0000</pubDate><atom:updated>2009-11-19T15:12:45.988+06:00</atom:updated><title>Инвайты на Google Wave</title><description>Кому нужны инвайты на Google Wave, отписываемся в комментариях - поделюсь.

&lt;span style="font-weight:bold;"&gt;UPD&lt;/span&gt;. Инвайты закончились, всем спасибо.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-8421063008720169663?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=FUtoxKUhDW0:4GilHHDmrwU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=FUtoxKUhDW0:4GilHHDmrwU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=FUtoxKUhDW0:4GilHHDmrwU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=FUtoxKUhDW0:4GilHHDmrwU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=FUtoxKUhDW0:4GilHHDmrwU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=FUtoxKUhDW0:4GilHHDmrwU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=FUtoxKUhDW0:4GilHHDmrwU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=FUtoxKUhDW0:4GilHHDmrwU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/FUtoxKUhDW0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/FUtoxKUhDW0/google-wave.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>12</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/11/google-wave.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-2039733364952658376</guid><pubDate>Tue, 07 Jul 2009 15:11:00 +0000</pubDate><atom:updated>2009-12-15T08:12:20.263+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">foreign key</category><category domain="http://www.blogger.com/atom/ns#">constraint</category><category domain="http://www.blogger.com/atom/ns#">mysql</category><category domain="http://www.blogger.com/atom/ns#">sybase</category><category domain="http://www.blogger.com/atom/ns#">база данных</category><category domain="http://www.blogger.com/atom/ns#">transaction</category><category domain="http://www.blogger.com/atom/ns#">разработка</category><category domain="http://www.blogger.com/atom/ns#">postgresql</category><category domain="http://www.blogger.com/atom/ns#">oracle</category><title>Двунаправленная ссылочная целостность</title><description>Здравствуйте.&lt;br /&gt;
&lt;br /&gt;
Я считаю, что разработчику всегда стоит иметь представление о том, как работают низлежащие абстракции. Хотя бы потому, что часто &lt;a href="http://c2.com/cgi/wiki?LeakyAbstraction"&gt;абстракции&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Leaky_abstraction"&gt;бывают&lt;/a&gt; &lt;a href="http://www.joelonsoftware.com/articles/LeakyAbstractions.html"&gt;протекающими&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
А веду я это к тому, что когда фреймворк вываливает стектрейс с несколькими вложенными (и, часто, для непосвященных непонятными) исключениями, то бывает непонятно, что с этим делать, особенно, если не знаешь, как реализована абстракция.&lt;br /&gt;
&lt;br /&gt;
Иногда бывает нужно хранить двунаправленные связи между таблицами. Например, есть игрок и есть его статистика и нужно, чтобы каждая из сущностей знала про другую.&lt;br /&gt;
&lt;br /&gt;
Вопрос - каким образом вставлять в базу данных такие сущности?&lt;br /&gt;
&lt;br /&gt;
При вставке в таблицу в Player нам нужно уже иметь запись в таблице Statistics, а для вставки в таблицу Statistics нужно знать идентификатор игрока, к которому относится эта запись.&lt;br /&gt;
&lt;br /&gt;
Выглядит замкнутым кругом, но решение есть.&lt;br /&gt;
&lt;p&gt;Во-первых, вставки в обе таблицы должны происходить в рамках одной транзакции (как я &lt;a href="http://www.blogger.com/2009/04/blog-post_24.html"&gt;уже писал&lt;/a&gt;, транзакция обеспечивает перевод базы данных из одного непротиворечащего состояния в другое непротиворечащее состояние).&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
К сожалению, этого недостаточно:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: sql"&gt;begin transaction;

INSERT INTO "Player"  VALUES (10, 20);
INSERT INTO "Statistics"  VALUES (20, 10);

commit;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
выдаёт следующую ошибку:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: plain"&gt;ERROR: insert or update on table "Player" violates foreign key constraint "statisticsId"
SQL state: 23503
Detail: Key (statisticsId)=(20) is not present in table "Statistics".
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
И снова кажется, что замкнутый круг не разорвать.&lt;br /&gt;
&lt;br /&gt;
Но решение всё же есть.&lt;br /&gt;
Многие базы данных позволяют нарушать консистентность базы данных внутри транзакции. В данном случае можно "попросить" базу данных отложить проверку ссылочной целостности. На примере PostgreSQL рассмотрим, как это работает.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;PostgreSQL&lt;/h3&gt;&lt;br /&gt;
&lt;pre class="brush: sql"&gt;begin transaction;

set constraints all deferred; -- 1

INSERT INTO "Player"  VALUES (10, 20);
INSERT INTO "Statistics"  VALUES (20, 10);

set constraints all immediate; -- 2

commit; -- 3
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
1 - Сделать все ограничения отложенными. Ограничения будут проверены в момент проведения фиксации транзакции (в строке 3)&lt;br /&gt;
2 - Явно делаем все ограничения немедленными. В данном примере это необязательно, так как в 3) они всё равно будут проверены.&lt;br /&gt;
&lt;br /&gt;
После выполнения данного кода получаем:&lt;br /&gt;
&lt;pre class="brush: plain"&gt;Query returned successfully with no result in 24 ms.
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Это уже полностью рабочий пример, чтобы это заработало нужно явно сделать ограничения отложенными - они должны быть объявлены как DEFERRED. Официальная документация к PostgreSQL гласит:&lt;br /&gt;
&lt;blockquote&gt;Upon creation, a constraint is given one of three characteristics: DEFERRABLE INITIALLY DEFERRED, DEFERRABLE INITIALLY IMMEDIATE, or NOT DEFERRABLE. The third class is always IMMEDIATE and is not affected by the SET CONSTRAINTS command. The first two classes start every transaction in the indicated mode, but their behavior can be changed within a transaction by SET CONSTRAINTS.&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Так что необходимо также модифицировать объявления ограничений:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;ALTER TABLE "Player"
ADD CONSTRAINT "statisticsId" FOREIGN KEY ("statisticsId")
REFERENCES "Statistics" ("statisticsId") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY IMMEDIATE;

&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Данный пример был приведён для PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
Для других баз данных решения следующие:&lt;br /&gt;
&lt;h3&gt;Sybase&lt;/h3&gt;&lt;br /&gt;
В Sybase есть переменная &lt;a href="http://manuals.sybase.com/onlinebooks/group-sasarc/awg0600e/dbugen6/@Generic__BookTextView/26075;hf=0;pt=25811"&gt;WAIT_FOR_COMMIT&lt;/a&gt;, которая управляет поведением проверки ограничений. По умолчанию она отключена. Кроме того, это поведение можно переопределить при объявлении можно указывать CHECK ON COMMIT. Подробнее можно посмотреть в &lt;a href="http://manuals.sybase.com/onlinebooks/group-pbarc/conn5/sqlug/@Generic__BookTextView/39397%3Bpt=39951"&gt;официальной документации&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Oracle&lt;/h3&gt;&lt;br /&gt;
Также, как и в PostgreSQL:&lt;br /&gt;
&lt;blockquote&gt;You can define constraints as either deferrable or not deferrable, and either initially deferred or initially immediate. These attributes can be different for each constraint. You specify them with keywords in the CONSTRAINT clause:&lt;br /&gt;
DEFERRABLE or NOT DEFERRABLE&lt;br /&gt;
INITIALLY DEFERRED or INITIALLY IMMEDIATE&lt;br /&gt;
&lt;br /&gt;
Constraints can be added, dropped, enabled, disabled, or validated. You can also modify a constraint's attributes.&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
Подробнее в &lt;a href="http://download.oracle.com/docs/cd/B10501_01/server.920/a96524/c22integ.htm#4666"&gt;официльной документации&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;mySQL&lt;/h3&gt;&lt;br /&gt;
К сожалению, mySQL не имеет возможности отложенной проверки ограничении, о чём в документации и сказано:&lt;br /&gt;
&lt;blockquote&gt;&lt;em&gt;Deviation from SQL standards&lt;/em&gt;: Like MySQL in general, in an SQL statement that inserts, deletes, or updates many rows, InnoDB checks UNIQUE and FOREIGN KEY constraints row-by-row. According to the SQL standard, the default behavior should be deferred checking. That is, constraints are only checked after the entire SQL statement has been processed. Until InnoDB implements deferred constraint checking, some things will be impossible, such as deleting a record that refers to itself via a foreign key.&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Но это не конец света, так как можно отключить проверку внешних ключей:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;SET foreign_key_checks = 0;
-- code goes here
SET foreign_key_checks = 1;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Чем эта информация может быть полезна для разработчика? Например, в случае, если ошибки и недоработки Hibernate покажут себя в виде стектрейса в логе(например, &lt;a href="http://opensource.atlassian.com/projects/hibernate/browse/HHH-2248"&gt;HHH-2248&lt;/a&gt;), то разработчик будет понимать причину ошибки и то, как реализовать work-around.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-2039733364952658376?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2iHSZJn5ABc:CKSKWi2pkHk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2iHSZJn5ABc:CKSKWi2pkHk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2iHSZJn5ABc:CKSKWi2pkHk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2iHSZJn5ABc:CKSKWi2pkHk:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2iHSZJn5ABc:CKSKWi2pkHk:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2iHSZJn5ABc:CKSKWi2pkHk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=2iHSZJn5ABc:CKSKWi2pkHk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=2iHSZJn5ABc:CKSKWi2pkHk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/2iHSZJn5ABc" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/2iHSZJn5ABc/blog-post.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>3</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/07/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8483146613266894707.post-4488864395166590348</guid><pubDate>Fri, 03 Jul 2009 18:13:00 +0000</pubDate><atom:updated>2009-11-22T02:10:31.327+06:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">delicious</category><category domain="http://www.blogger.com/atom/ns#">opera</category><title>Opera и del.icio.us</title><description>Так получилось, что в последнее время я стал пользоваться небезызвестной &lt;a href="http://www.opera.com"&gt;оперой&lt;/a&gt;.

В связи с этим я стал искать замену часто используемым расширениям, одно из них - delicious bookmarks. Сервис &lt;a href="http://delicious.com"&gt;del.icio.us&lt;/a&gt; предоставляет букмарклеты для различных браузеров, которые позволяют сохранять закладки быстро и удобно. Вот только есть один недостаток - &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/3615"&gt;официальный плагин для Firefox&lt;/a&gt; в свойство Notes новой закладки добавляет выделенный текст со страницы, чего букмарклет не делает.

Посмотрев исходник букмарклета, а это обычный javascript, я добавил небольшой кусочек кода, который восстанавливает справедливость и тоже добавляет в поле Notes текст, выделенный на странице.

Ниже привожу изменённый код букмарклета:
&lt;pre class="brush:js"&gt;
javascript:(function(){f='http://delicious.com/save?url='+encodeURIComponent(window.location.href)+'&amp;title='+encodeURIComponent(document.title)+'&amp;notes='+encodeURIComponent(document.getSelection())+'&amp;v=5&amp;';a=function(){if(!window.open(f+'noui=1&amp;jump=doclose','deliciousuiv5','location=yes,links=no,scrollbars=no,toolbar=no,width=550,height=550'))location.href=f+'jump=yes'};if(/Firefox/.test(navigator.userAgent)){setTimeout(a,0)}else{a()}})()
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8483146613266894707-4488864395166590348?l=atamanenko.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=ddvRyQ-WoAk:hBsTOVkUmQA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=ddvRyQ-WoAk:hBsTOVkUmQA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=ddvRyQ-WoAk:hBsTOVkUmQA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=ddvRyQ-WoAk:hBsTOVkUmQA:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=ddvRyQ-WoAk:hBsTOVkUmQA:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=ddvRyQ-WoAk:hBsTOVkUmQA:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/atamanenko?a=ddvRyQ-WoAk:hBsTOVkUmQA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/atamanenko?i=ddvRyQ-WoAk:hBsTOVkUmQA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/atamanenko/~4/ddvRyQ-WoAk" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/atamanenko/~3/ddvRyQ-WoAk/opera-delicious.html</link><author>noreply@blogger.com (Oleg Atamanenko)</author><thr:total>2</thr:total><feedburner:origLink>http://atamanenko.blogspot.com/2009/07/opera-delicious.html</feedburner:origLink></item></channel></rss>

