Stripes Framework

Mar 20, 2011 20:51



Stripes - это это фреймворк для разработки веб-приложений с открытым исходным кодом, построенный по модели MVC. Если вникнуть в его суть, то можно быстро и красиво писать web-приложения. Он предоставляет объектно-ориентированный доступ к представлениям. Рассмотрим устройство этого фреймворка на примере простого приложения для заполнения регистрационной формы. Для разработки будем использовать Intellij IDEA. Перед работой необходимо поставить плагин IntelliStripes. Создадим новый проект и поставим галочку напротив пункта Web application.

Приложение Stripes состоит из нескольких основных частей:


  • контроллеры (ActionBean);
     
  • представления (jsp);
     
  • модели (model);
     
  • интерсепторы (Interceptor);
     
  • конвертеры типов (TypeConverter);
     
  • форматтеры (Formatter).
     


Исходный код Stripes доступен и неясные моменты всегда можно прояснить с его помощью, потому что он, с моей точки зрения, очень качественный и легко расширяемый средствами ООП.
Скачаем последнюю версию фреймворка официального сайта.
В каталог lib кладем файлы stripes.jar и commons-logging.jar из скачанного архива и добавляем их к проекту через меню File->Project Structure->Libraries.
Структура проекта в простейшем общем случае должна выглядеть так:


Кроме этой библиотеки нужно добавить:


  • jstl.jar (для общей поддержки jsp);

  • standard.jar (для тега c:forEach, который мы будем использовать);

  • mail.jar (для валидации введенного email средствами stripes).



Добавляем конфигурацию stripes в web.xml. Конфигурация представляет собой два фильтра и один маппинг фильтра:

xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

StripesFilterdisplay-name>
StripesFilterfilter-name>
net.sourceforge.stripes.controller.StripesFilterfilter-class>

ActionResolver.Packagesparam-name>
com.example.actionparam-value>
init-param>

Extension.Packagesparam-name>
com.example.extparam-value>
init-param>
filter>

DynamicMappingFilterfilter-name>
net.sourceforge.stripes.controller.DynamicMappingFilterfilter-class>
filter>

DynamicMappingFilterfilter-name>
*.actionurl-pattern>
REQUESTdispatcher>
filter-mapping>
web-app>

У фильтра StripesFilter основной параметр ActionResolver.Packages, в котором задается имя пакета, содержащего контроллеры. В параметре Extension.Packages указывается путь к дополнительным классам конвертеров типов или форматтеров, которые Stripes автоматически подключает при старте приложения.

Создадим для контроллеров пакет com.example.action. В этом пакете создадим класс FormActionBean, который реализует интерфейс ActionBean. Контроллер с таким именем будет доступен по URL: http://localhost:8080/stripes/Form.action.

Контроллер по мнению stripes должен реализовывать по крайней мере два метода: getContext и setContext. Создаем приватное поле context класса ActionBeanContext и делаем к нему геттер и сеттер. Очень удобно для каждого приложения создавать свой класс контекста, в котором хранить текущие параметры запроса/пользователя, например объекты DAO, параметры сессии (чтобы приведение их к типу было локализовано в одном месте). Объект контроллера и объект контекста создается новый на каждый запрос, так что они изолированы между различными запросами и пользователями.

Создадим модель пользователя User в классе com.example.model.User. Пользователь будет иметь имя, дату рождения, электронный адрес и страну проживания. Это будет обычный бин POJO. У него обязательно должен быть публичный конструктор без параметров, если определены другие, чтобы stripes смог создать его во время стадии биндинга переменных.

package com.example.model;

import java.util.Date;

public class User {

private String name;
private Date birthDate;
private String email;
private String country;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthDate() {
return birthDate;
}

public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

@Override
public String toString() {
return name + " " + birthDate + " " + email + " " + country;
}
}

Для того, чтобы данные пользователя из jsp записались в объект, нужно создать в контроллере приватное поле типа User и геттер и сеттер к нему. Код контроллера:

package com.example.action;

import com.example.Data;
import com.example.model.User;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.validation.DateTypeConverter;
import net.sourceforge.stripes.validation.EmailTypeConverter;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import java.util.Collection;

public class FormActionBean implements ActionBean {

private static String[] countries = new String[]{"Russia", "America"};

@ValidateNestedProperties({
@Validate(field = "name", required = true, minlength = 10, on = "add"),
@Validate(field = "birthDate", converter = DateTypeConverter.class, on = "add"),
@Validate(field = "email", required = true, converter = EmailTypeConverter.class, on = "add"),
@Validate(field = "country", required = true, on = "add")
})
private User user;

private ActionBeanContext context;

public void setContext(ActionBeanContext actionBeanContext) {
this.context = actionBeanContext;
}

public ActionBeanContext getContext() {
return context;
}

@DefaultHandler
public Resolution view() {
return new ForwardResolution("WEB-INF/page.jsp");
}

public Resolution add() {
Data.users.put(user.getName(), user);
return new RedirectResolution(FormActionBean.class);
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public String[] getCountries() {
return countries;
}

public Collection getUsers() {
return Data.users.values();
}
}

Разберем подробнее код контроллера. Когда пользователь делает GET-запрос по URL Form.action, Stripes вызывает обработчик по умолчанию, помеченный аннотацией @DefaultHandler. В терминологии Stripes имя обработчика называется event. Если нужно вызвать другой обработчик, то его имя должно быть одним из параметров запроса, например так: "Form.action?add=". В этом случае вызовется метод add класса FormActionBean.

Метод getCountries нужен для вывода списка стран на странице, а метод getUsers - для вывода добавленных пользователей. С помощью класса Data мы эмулируем постоянное хранилище (типа базы данных) и для простоты используем потокобезопасный мап пользователей по имени. Код класса Data:

package com.example;

import com.example.model.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Data {
public static Map users = Collections.synchronizedMap(new HashMap());
}

Открывая страницу по адресу http://localhost:8080/stripes/Form.action мы попадаем в метод view(), который ни делает в данном случае ничего, кроме того, что передает управление коду рендеринга jsp-страницы. Код страницы page.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>


Stripes Frameworktitle><br />head><br /><body><br /><s:messages/><br /><s:errors/><br /><s:form beanclass="com.example.action.FormActionBean"><br /> Имя: <s:text name="user.name" value="Foo Bar Buzzovich"/> <br /><br /> Дата рождения: <s:text name="user.birthDate" value="01.01.1985"/> <br /><br /> Электронный адрес: <s:text name="user.email" value="user@example.com"/> <br /><br /> Страна: <br /> <s:select name="user.country"><br /> <s:options-collection collection="${actionBean.countries}"/><br /> s:select> <br /><br /> <s:submit name="add" value="Добавить"/><br />s:form><br /><h1>Добавленные пользователиh1><br /><c:forEach items="${actionBean.users}" var="user"><br /> <s:format value="${user}"/> <br /><br /> c:forEach><br />body><br />html> <br /><br /> Stripes заменит свои теги на html, подставит текущие значения полей контроллера и их вложенных полей. Так как при вызове из метода view поле user равно null, то в форму вставятся значения по умолчанию из атрибутов value. Параметр beanclass у формы определяет метод какого класса вызывать при нажатии кнопки, помеченной как submit. В данном случае кнопка submit имеет атрибут name, равный add, что означает, что при нажатии на нее вызывется метод add и все параметры формы отправятся POST-запросом на сервер. <br /><br /> POST-запрос в данном случае будет выглядеть примерно так: <br /><br /> user.name=Foo+Bar+Buzzovich&user.birthDate=01.01.1985&user.email=user%40example.com&<br />user.country=Russia&add=%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C&<br />_sourcePage=WD-cxfwrDfHq57V_5vLKPcjZ6ep322Ej&__fp=HX9golAsBE8d0xswK8OSb0z4QsxFsxhx <br /><br /> Перед вызовом метода add все параметры будут преобразованы в строки и объекты Java и создан объект класса User. Например, дата рождения станет из строки объектом класса Date благодаря указанию в аннтоации к этому полю параметра converter. Метод add добавит созданного Stripes пользователя в мап и сделает редирект снова на метод view: RedirectResolution вернет в браузер 302 и заголовок "Location: <a href="http://localhost:8080/stripes/Form.action" class="external">http://localhost:8080/stripes/Form.action</a>". <br /><br /> Если сделать запрос вида "Location: <a href="http://localhost:8080/stripes/Form.action?user=Foo%20Bar%20Buzzovich" class="external">http://localhost:8080/stripes/Form.action?user=Foo%20Bar%20Buzzovich</a>", то сработает конвертер типов для класса User: <br /><br /> package com.example.ext; <br /><br /> import com.example.Data;<br />import com.example.model.User;<br />import net.sourceforge.stripes.format.Formatter;<br />import net.sourceforge.stripes.validation.TypeConverter;<br />import net.sourceforge.stripes.validation.ValidationError;<br />import java.util.Collection;<br />import java.util.Locale; <br /><br /> public class UserTCF implements TypeConverter, Formatter { <br /><br /> public void setFormatType(String s) {<br /> } <br /><br /> public void setFormatPattern(String s) {<br /> } <br /><br /> public void init() {<br /> } <br /><br /> public String format(User user) {<br /> return user.toString();<br /> } <br /><br /> public void setLocale(Locale locale) {<br /> } <br /><br /> public User convert(String name, Classextends User> aClass, Collection validationErrors) {<br /> return Data.users.get(name);<br /> }<br />} <br /><br /> В этом классе объединены одновременно и конвертер из строки и форматтер в строку (они не обязательно должны быть противоположны по логике преобразования). Stripes увидит, что в контроллере есть поле user с именем таким же, что и в параметре запроса и вызовет конвертер типов класса этого поля, в данном случае User. Метод convert по строке найдет в мапе нужного пользователя и вернет его, и он присвоится в поле user. <br /><br /> После срабатывания конвертера типов поле user уже будет заполнено нужным объектом и при отображении страницы выведутся значения из этого объекта. <br /><br /> Ниже формы на jsp-странице выводится список всех уже добавленных пользователей. При этом используется тег stripes format. В данном случае, он фиктивен и вызывает метод toString объекта User, просто для примера использования форматтера.<br /> </div> </div> <div class= " ljsale ljsale--empty " lj0sale="adfox_mobile_content_1" ></div> <div class="b-more-button"> <a class="b-more-button-inner" href="/login?back_uri=http://m.livejournal.com/read/user/stas_agarkov/22143/comments/reply#comments">Leave a comment</a></p> </div> </div> </div> <div class="paging"> <table cellspacing="0" cellpadding="0"> <tbody> <tr> <td class="paging-prev"> <a href="http://m.livejournal.com/read/user/stas_agarkov/21904">Previous post</a> </td> <td class="paging-next"> <a href="http://m.livejournal.com/read/user/stas_agarkov/17012">Next post</a> </td> </tr> </tbody> </table> </div> <div class="to-top"> <a href="#to-top"> Up </a> </div> <div class="footer"> <div class= " ljsale ljsale--empty " lj0sale="adfox_mobile_footer" ></div> <div class="footer-content"> <table class="footer-layout"> <tr> <td> <a class="footer-menu-item footer-menu-index" href="//m.livejournal.com/"> <span class="footer-menu-item-title">Homepage</span> </a> </td> <td> <a class="footer-menu-item footer-menu-read" href="//m.livejournal.com/read"> <span class="footer-menu-item-title">Read journal...</span> </a> </td> <td> <a class="footer-menu-item footer-menu-full" href="https://stas_agarkov.livejournal.com/22143.html?fullversion=yes"> <span class="footer-menu-item-title">Full version</span> </a> </td> <td> <a class="footer-menu-item footer-menu-help" href="//m.livejournal.com/help"> <span class="footer-menu-item-title">Help</span> </a> </td> <td> <a class="footer-menu-item footer-menu-eng" href="//www.livejournal.com/tools/setlang.bml?lang=ru&returnto=//m.livejournal.com/read/user/stas_agarkov/22143"> <span class="footer-menu-item-title">Русская версия</span> </a> </td> </tr> </table> <link rel="stylesheet" type="text/css" href="https://l-stat.livejournal.net/??lj_base.css,flatbutton.css,mobile/default/s.css,msgsystem.css?v=1735041763" > <!--[if lte IE 8]><link rel="stylesheet" type="text/css" href="https://l-stat.livejournal.net/??ie.css?v=1735041763" ><![endif]--> <link rel="stylesheet" type="text/css" href="https://l-stat.livejournal.net/??proximanova-opentype.css?v=1735041763" > <script type="text/javascript"> Site = window.Site || {}; Site.ml_text = {"like_reaction.pencil.caption":"Keep writing!","like_reaction.facepalming.caption":"Facepalm","/userinfo.bml.editalias.title":"Edit Note","modal.emailreconfirm.button.accept":"Yes, this is my address","common.something_went_wrong":"Something went wrong","like_reaction.pigeon.caption":"Strange things","common.edit":"Edit","notif_center.repost.user_and_user":"<strong>[[actor0]]</strong> and <strong>[[actor1]]</strong> reposted your entry <a href=\"#\">[[post_subject]]</a>","notif_center.birthday":"Today is <strong>[[display_name]]</strong>'s birthday!","subscribe_button_2022.you_are_owner":"You are owner","common.remove_from_friends":"Remove from friends","notif_center.pingback.comment":"<strong>[[actor]]</strong> mentioned you in a comment for the entry <a href=\"#\">[[post_subject]]</a>","modal.badge.verified.button.text":"Read more","sharing.service.vkontakte":"VKontakte","common.back":"Back","modal.gift_token.description":"The size of the commission is [[commission]] tokens; after you'll have [[token]] tokens left","medius.asapnews.breakingnews":"Срочная новость","components.report_modal.description.alco":"Information containing offers of remote retail sale of alcoholic products whose retail sale is either forbidden or restricted by law.","popup.cookies.submit":"OK","like_reaction.like.caption":"Like","common.save":"Save","adfox.noads":"Tired of ads? Upgrade to account with Professional package of service and never see ads again!","common.delete":"Delete","components.report_modal.description.involving_minors":"Information aimed at involving minors in illegal activity dangerous for their life and(or) health.","popup.cookies.conditionsLink":"conditions","notif_center.trending_now_custom_url":"Trending now: <a href=\"#\">[[subject]]</a>","sharing.popup.title":"Share","like_reaction.detail_popup.add_btn.is_added":"Is friend","subscribe_button_2022.join_community":"Join community","common.are_you_sure":"Are you sure?","widget.addalias.display.helper":"Visit the <a [[aopts]]>Manage Notes</a> page to manage all your user notes.","modal.gift_token.button.send":"Send","sharing.service.facebook":"Facebook","components.report_modal.description.suicide":"Calls for suicide or self-harm, demonstration of suicide.","component.messages.close":"Close","notif_center.comment.to_comment":"<strong>[[actor]]</strong> replied to your comment for entry <a href='#'>[[post_subject]]</a>","sherry_promo_button.tooltip_text":"Реклама. ООО «Перфлюенс». ИНН 7725380313. erid: 2Ranym4Vj3N","dialogs.no":"Cancel","sharing.service.twitter":"X","components.report_modal.already_reported":"You've already reported a breach","notif_center.promo_feed":"Check out <a href=\"#\">new interesting posts</a> for your personal feed!","pushWooshPopup21.description":"Enable browser push notifications about new entries","subscribe_button_2022.add_user_note":"Add a note","notif_center.like.entry.user_and_user":"<strong>[[actor0]]</strong> and <strong>[[actor1]]</strong> reacted to your entry <a href=\"#\">[[post_subject]]</a>","recurrentreminder.password.updated.last":"updated password <strong>more than [[time]] ago</strong>","modal.gift_token.message.warning.insufficient_tokens":"You don't have enough tokens. <a href=\"https://www.livejournal.com/shop/tokens.bml?ila_compaign=gift_tokens&ila_location=gift_modal\">Buy tokens</a>","notif_center.new_friend_achievement":"Your friend <strong>[[actor_displayname]]</strong> got a new achievement! <a href=\"#\">[[achievement_display]]</a>","subscribe_button_2022.you_are_subscribed":"You are subscribed","fcklang.ljlike.button.whatsapp":"WhatsApp","subscribe_button_2022.edit_user_note":"Edit the note","lj.report.popover.message":"Thanks! Moderators will review your complaint","notif_center.pingback.comment.community":"<strong>[[actor]]</strong> mentioned your community [[community]] in a comment for entry <a href=\"#\">[[post_subject]]</a>","notif_center.poll.vote.user_and_user":"<strong>[[actor0]]</strong> and <strong>[[actor1]]</strong> voted in poll in your post <a href=\"#\">[[post_subject]]</a>","pushWooshPopup21.button.close":"No","subscribe_button_2022.user_note.not_available_for_basic_users":"<a href=\"https://www.livejournal.com/shop/profaccount/?ila_campaign=prof_account&ila_location=add_note\" target=\"_blank\">Upgrade your account</a> to add a note","modal.gift_token.suggestion.popup.text.hint":"The commission will be [[commission]] [[?commission|token|tokens|tokens]], after you will have [[token]] [[?token|token|tokens|tokens]].","mobileAppBanners.betterCommunicateInApp.title":"<p><b>Communicate</b><p><p>is easier in mobile app</p>","create.subscribe.proceed_btn.caption":"Proceed","modal.info_pro.title_notpaid":"Account without the \"Professional\" package","sharing.service.moimir":"Moi mir","recurrentreminder.password.button.update.password_short":"Yes","common.unsubscribe":"Unsubscribe","modal.info_pro.feature.item.filter_comment":"Negative comment filter","like_reaction.dislike.caption":"Dislike","notif_center.comment.anon":"Anonymous user commented an entry <a href=\"#\">[[post_subject]]</a>","notif_center.like.comment.user_and_user":"<strong>[[actor0]]</strong> and <strong>[[actor1]]</strong> reacted to your <a href=\"#\">comment</a>","createaccount.subscribe.title":"Recommended authors","modal.info_pro.feature.item.notification":"Additional notification settings","fcklang.ljlike.button.copyURL":"Copy url","notif_center.new_achievement":"<strong>New achievement!</strong> <a href=\"#\">[[achievement_display]]</a> [[achievement_description]]","notif_center.comment":"<strong>[[actor]]</strong> commented an entry <a href='#'>[[post_subject]]</a>","subscribe_button_2022.you_are_member":"You are member","recurrentreminder.email.message":"<span>Your account is linked to mail <strong>[[email]]</strong> Is this address still up to date?</span>","notif_center.settings":"Settings","sitescheme.switchv3.confirm_dialog.no":"No, stay on the old one","modal.info_pro.feature.item.photo":"More photo storage space","notif_center.post_suggest.no_subj":"We've found a <a href=\"#\">post</a> that might be interesting for you","createaccount.subscribe.description":"You might like the authors","modal.info_pro.button":"Learn more","error_adding_friends.email_not_validated":"Sorry, you aren't allowed to add to friends until your email address has been validated. If you've lost the confirmation email to do this, you can <a href=\"http://www.livejournal.com/register.bml\">have it re-sent.</a>","notif_center.like.entry.plur":"<strong>[[actor]]</strong> and [[other_n]] [[?other_n|user|users|users]] reacted to <a href=\"#\">[[post_subject]]</a>","sharing.service.embed":"Embed","user_note_modal.title.add":"Add a note","components.report_modal.too_many_complaints":"You've reached report limit per day","notif_center.poll.vote.plur":"<strong>[[actor]]</strong> and [[other_n]] [[?other_n|user|users|users]] voted in poll in your post <a href=\"#\">[[post_subject]]</a>","notif_center.trending_now.no_subj":"<a href=\"#\">New post</a> is trending now","components.report_modal.descr":"Pick a category of complaint:","dialogs.yes":"OK","modal.gift_token.buy":"Purchase LJ Tokens to gift","modal.badge.verified.title":"Verified log","subscribe_button_2022.is_in_friend_list":"In friend list","notif_center.offcialpost":"Some news for you: <a href=\"#\">[[post_subject]]</a>","notif_center.user_post.in_community":"User <strong>[[actor]]</strong> published a new entry <a href=\"#\">[[post_subject]]</a> to community <strong>[[comm_disp_name]]</strong></a>","components.report_modal.description.adult_content":"Explicit graphic content only intended for viewers aged 18 and up.","common.closing_quote":"\"","notif_center.dropdown.hide":"Unsubscribe","banner.hashmob.favorite_cities.button":"participate","common.recommended.description":"We selected these authors and communities for you","create.head":"Creating a New Journal","components.report_modal.description.lgbt_propaganda":"Information aimed at involving minors in illegal activity demonstratingsuicide or justifying non-traditional values.","sharing.service.email":"E-mail","widget.recomended.partners.title":"Partner news","modal.emailreconfirm.button.cancel_short":"No","banner.hashmob.favorite_cities.hash":"hashmob","notif_center.social_connections.subscription":"<a href=\"#\">[[actor]]</a> subscribed to you","notif_center.repost":"<strong>[[actor]]</strong> reposted your entry <a href=\"#\">[[post_subject]]</a>","modal.emailreconfirm.button.cancel":"No, change","common.add_to_friends":"Add to friends","pushWooshPopup21.button.subscribe":"Yes, please","recurrentreminder.password.message":"<span>We noticed that you [[messageData]]. Increase the security of your account by setting a new one</span>","modal.info_pro.feature.item.seo":"SEO tools","like_reaction.thumbs_up.caption":"Thumbs up","banner.hashmob.favorite_cities.name":"#favoritecities","modal.info_pro.user_notpaid":"does not yet use the \"Professional\" service package","pushWooshPopup21.title":"You can subscribe to journal","you_are_logged_in_hint.button.cancel":"Cancel","modal.gift_token.text":"How many LJ Tokens do you want to send to user","modal.emailreconfirm.button.accept_short":"Yes","/userinfo.bml.addalias.title":"Add Note","common.opening_quote":"\"","modal.info_pro.title":"An account with the active Professional service package","notif_center.comment.to_comment.anon":"Anonymous user replied to your comment for entry <a href='#'>[[post_subject]]</a>","recurrentreminder.password.button.refuse_short":"No","subscribe_button_2022.leave_community":"Leave community","notif_center.repost.plur":"<strong>[[actor]]</strong> and [[other_n]] [[?other_n|user|users|users]] reposted your entry <a href=\"#\">[[post_subject]]</a>","sitescheme.switchv3.confirm_dialog.yes":"Yes, switch to the new one","sharing.service.viber":"Viber","user_note_modal.add_note_for":"Only you will see this note on hover the username: ","like_reaction.fire.caption":"Hot","notif_center.this_day":"Remember what you wrote on this day, <a href=\"#\">[[date]]</a> in the past!","notif_center.continuous_series.start":"You wrote a post yesterday. That's a good start! Write a post today ([[date]]) to extend the uninterrupted series.","sharing.service.stumbleupon":"StumbleUpon","notif_center.pingback.entry.community":"<strong>[[actor]]</strong> mentioned your community [[community]] in entry <a href=\"#\">[[post_subject]]</a>","identity.system.message.title":"This account was created using a social network or other third-party service.<br />We recommend you to choose a LiveJournal login and set a password for your account so you don't need to rely on third-party services for login.<br />You can do this by following this <a id=\"identity-message\" target=\"_blank\" href=\"https://www.livejournal.com/identity/convert.bml\">link</a>","recurrentreminder.password.updated.months.ago":"[[amount]] months","components.report_modal.title":"Report","like_reaction.detail_popup.button.close":"Close popup","like_reaction.pow_prints.caption":"Paw prints","fcklang.ljlike.button.email":"Email","like_reaction.detail_popup.all":"All [[count]]","components.report_modal.link.illegal":"What materials are forbidden in LiveJournal?","notif_center.dropdown.mark":"mark as read","recurrentreminder.password.button.update.password":"Yes, I want to update","like_reaction.laughing.caption":"LOL","like_reaction.detail_popup.title":"Reactions","notif_center.dropdown.delete":"Delete","modal.info_pro.feature.text":"The Professional package grants you the following perks:","modal.badge.verified.button.link":"https://www.livejournal.com/support/faq/442.html?ila_campaign=verified&ila_location=badge_modal","you_are_logged_in_hint.button.reboot":"Refresh","createaccount.subscribe.to.post":"To first post","sharing.service.digg":"Digg","notif_center.view_all.label":"View all","recurrentreminder.password.button.refuse":"No, I'm satisfied","like_reaction.ok_hand.caption":"OK","notif_center.read_all.label":"Mark all as read","like_reaction.nauseated_face.caption":"Nauseated face","notif_center.like.comment.plur":"<strong>[[actor]]</strong> and [[other_n]] [[?other_n|user|users|users]] reacted to your <a href=\"#\">comment</a>","modal.gift_token.button":"Send LJ Tokens","notif_center.title":"Notifications","userinfo.bml.hover_menu.paid":"Account with the <a href=\"https://www.livejournal.com/shop/profaccount/?ila_campaign=prof_sign&ila_location=context_hover\" class=\"contextual-link\" target=\"_blank\">Professional</a> package of service","user_note_modal.title.edit":"Edit user note","like_reaction.angry.caption":"Angry","notif_center.continuous_series":"You have been writing for [[days]] [[?days|day|days|days]] now. Write a post today ([[date]]) and extend the uninterrupted series!","components.report_modal.description.drugs":"Information on ways of producing, using, and places of purchasing of narcotic substances.","notif_center.post_suggest.recent_journal_upd":"There is a new post in journal which you've visited recently: <a href=\"#\">[[post_subject]]</a>","notif_center.like.entry":"<strong>[[actor]]</strong> reacted to your entry <a href=\"#\">[[post_subject]]</a>","banner.native_install_prompt.ios.text":"Install LiveJournal app for IOS","widget.alias.aliasdelete":"Delete note","notif_center.pingback.entry":"<strong>[[actor]]</strong> mentioned you in the entry <a href=\"#\">[[post_subject]]</a>","popup.cookies.title":"This website uses cookies.","components.report_modal.description.hate_speech":"Expression of hatred towards against people based on their race, ethnicity, religion, gender, etc. ","notif_center.message":"<strong>[[actor]]</strong> sent you a <a href=\"#\">message</a>:","notif_center.post_suggest.recent_journal_upd.no_subj":"There is a new <a href=\"#\">post</a> in journal which you've visited recently","mobileAppBanners.footer.text":"Get LJ mobile app","modal.gift_token.clue":"The commission will be [[commission]] [[?commission|token|tokens|tokens]], after you will have [[token]] [[?token|token|tokens|tokens]].","notif_center.draft":"You have an unpublished draft, continue working on your new entry","adfox.noads.paid":"Log in to stop seeing ads in this journal","components.report_modal.description.spam":"Submit a complaint if someone has posted an ad in an inappropriate location.","banner.native_install_prompt.android.text":"Install LiveJournal app for Android","modal.info_pro.feature.item.style":"Advanced style settings","notif_center.poll.vote":"<strong>[[actor]]</strong> voted in poll in your post <a href=\"#\">[[post_subject]]</a>","banner.hashmob.favorite_cities.link":"https://www.livejournal.com/lyubimyye-goroda/?ila_campaign=lyubimyye_goroda&ila_location=banner","components.report_modal.description.fake":"Information containing calls for mass riots and(or) extremist activity that may endanger lives and(or) wellbeing of people, property, disruption of public order and(or) public safety.","modal.emailreconfirm.title":"Email confirmation","components.report_modal.link.other":"Other","notif_center.post_suggest":"This might be interesting for you: <a href=\"#\">[[post_subject]]</a>","like_reaction.poop.caption":"Poop","modal.info_pro.feature.item.adv":"No ads","common.subscribe":"Subscribe","notif_center.like.comment":"<strong>[[actor]]</strong> reacted to your <a href=\"#\">comment</a>","modal.info_pro.feature.more":"And many other extra functions more!","common.add_to_group":"Add to group","like_reaction.ghost.caption":"Ghost","sherry_promo_button.text.sherry":"СКИДКИ","subscribe_button_2022.subscribe_settings":"Manage subscription","common.close":"Close","sharing.service.odnoklassniki":"Odnoklassniki","entry.url_copied.message":"Entry url was copied to clipboard","common.recommended":"Suggested for you","recurrentreminder.password.updated.never":"<strong>never</strong> updated your password","notif_center.trending_now":"Trending now: <a href=\"#\">[[post_subject]]</a>","banner_popup.open_app":"Open App","modal.info_pro.feature.item.icon":"Badge by the username","widget.alias.aliaschange":"Save note","sherry_promo_button.link":"https://sharrymania.ru/?utm_source=livejournal&utm_medium=special_project&utm_campaign=sharry&utm_content=branding_block_2","sharing.service.livejournal":"LiveJournal","rambler.partners.title":"Today's News","fcklang.ljlike.button.telegram":"Telegram","popup.cookies.description":"By continuing to use this website, you agree to the","notif_center.user_post":"User [[actor]] published a new entry <a href=\"#\">[[post_subject]]</a>","sharing.service.tumblr":"Tumblr","modal.gift_token.title":"Gift LJ Tokens","common.and":"and","components.report_modal.description.extremism":"Calls for unrest and terror, violence against people of a specific ethnicity, religion, or race.","modal.info_pro.feature.item.statistic":"Advanced statistics","notif_center.time.now":"just now","createaccount.subscribe.to.feed":"To feed","like_reaction.face_vomiting.caption":"Face vomiting","notif_center.empty.label":"You don't have notifications","components.report_modal.description.insult_govsymbols":"Information that offends human dignity and public morale; explicit disrespect for society, state, official state symbols.","modal.gift_token.button.back":"Cancel","subscribe_button_2022.join_request_is_pending":"Join request is pending","modal.gift_token.message.success":"LJ Tokens has been sent successfully","recurrentreminder.password.updated.years.ago":"[[amount]] [[?amount|year|years]]","recurrentreminder.password.title":"Your password might be out of date","components.report_modal.description.gambling":"Information violating the demands of the Federal law on prohibition of gambling and lotteries via the Internet or other means of communication.","components.report_modal.description.child_porn":"Materials with pornographic depiction of minors, or involvement of minors in entertainment activities of pornographic nature.","modal.info_pro.user":"is using an account with the active Professional service package","widget.alias.faq":"read <a [[aopts]]>FAQ</a> for details","pwa.banner.android.text":"Add [[journal]] to Home screen","like_reaction.sad.caption":"Sad","modal.badge.verified.content":"The verified journal status means that the blog is officially maintained on behalf of a famous person or an organisation.<br /><br /><b>You also could receive a checkmark</b>, if you already have a verified status on another social platforms","banner.new_year_2025.link":"[[siteroot]]/newyear2025?ila_campaign=ny25&ila_location=banner","modal.gift_token.suggestion.popup.text":"You want to send [[value]] [[?value|token|tokens|tokens]] to the user","notif_center.whosback":"<strong>[[actor]]</strong> posted for the first time in [[delta]]: <a href=\"#\">[[post_subject]]</a>","notif_center.social_connections.friending":"<a href=\"#\">[[actor]]</a> add you as a friend","user_note_modal.edit_note_for":"Only you will see this note on hover the username: ","widget.alias.setalias":"Set note for","modal.emailreconfirm.confirmed":"Ок, thanks for checking the relevance of your data","you_are_logged_in_hint.hint_text_2":"You've signed in using another tab or window. Refresh this page","subscribe_button_2022.mutual_subscribe":"Mutual subscribe","components.report_modal.submit_report_caption":"Report","like_reaction.detail_popup.add_btn.add":"Add friend","userinfo.bml.hover_menu.headlinks.write_to_community":"Post to community"}; Site.page = {"adv_libs":{"google":{"url":"https://l-stat.livejournal.net/js/??ads/googletag.js?v=1735041763"},"ssp":{"conflicts":["adfox"]},"inner":{},"adfox":{"conflicts":["ssp"],"url":null}},"D":{},"is_mobile_version":1}; Site.page.template = {}; Site.page.ljlive = {"is_enabled":false}; Site.page.adv = {"adfox_mobile_listing_1":{"use_lib":"ssp","options":{"puid3":"","puid4":"NO","puid21":"NO","puid10":"bot","puid14":"NO","puid1":"","puid34":"","puid6":"LIVEJOURNAL_JOURNAL","puid15":"","puid16":"","puid18":"","puid7":"","puid9":"stas_agarkov","puid62":0,"puid59":"","puid2":"","puid8":""},"method":"sspScroll","options_begun":{"begun-block-id":"579314164","begun-auto-pad":"536695699"}},"adfox_native_1":{"use_lib":"ssp","options":{"puid3":"","puid4":"NO","puid21":"NO","puid10":"bot","puid14":"NO","puid1":"","puid34":"","puid6":"LIVEJOURNAL_JOURNAL","puid15":"","pct":"a","puid16":"","puid18":"","puid7":"","p1":"buuvt","puid62":0,"puid9":"stas_agarkov","puid59":"","puid2":"","p2":"fhzr","puid8":""},"method":"sspScroll","options_begun":{"begun-block-id":"432172008","begun-auto-pad":"432171792"}},"adfox_mobile_footer":{"use_lib":"ssp","options":{"puid3":"","puid4":"NO","puid21":"NO","puid10":"bot","puid14":"NO","puid1":"","puid34":"","puid6":"LIVEJOURNAL_JOURNAL","puid15":"","puid16":"","puid18":"","puid7":"","puid9":"stas_agarkov","puid62":0,"puid59":"","puid2":"","puid8":""},"method":"ssp","options_begun":{"begun-block-id":"579314162","begun-auto-pad":"536695699"}},"adfox_mobile_listing_2":{"use_lib":"ssp","options":{"puid3":"","puid4":"NO","puid21":"NO","puid10":"bot","puid14":"NO","puid1":"","puid34":"","puid6":"LIVEJOURNAL_JOURNAL","puid15":"","puid16":"","puid18":"","puid7":"","puid9":"stas_agarkov","puid62":0,"puid59":"","puid2":"","puid8":""},"method":"sspScroll","options_begun":{"begun-block-id":"579314166","begun-auto-pad":"536695699"}},"billboard_mobile":{"use_lib":"ssp","options":{"puid3":"","puid4":"NO","puid21":"NO","puid10":"bot","puid14":"NO","puid1":"","puid34":"","puid6":"LIVEJOURNAL_JOURNAL","puid15":"","puid16":"","puid18":"","puid7":"","puid9":"stas_agarkov","puid62":0,"puid59":"","puid2":"","puid8":""},"method":"ssp","options_begun":{"begun-block-id":"579314160","begun-auto-pad":"536695699"}}}; Site.page.is_adult = 0; Site.timer = +(new Date()); Site.remote = null; Site.journal = {"journal_url":"https://stas-agarkov.livejournal.com/","webpush_sub_enabled":false,"is_personal":true,"userhead_url":"https://l-stat.livejournal.net/img/userinfo_v8.svg?v=17080?v=809","is_syndicated":false,"has_photopackage":false,"badge":null,"journal_subtitle":"","is_paid":false,"id":10141809,"webvisor_enabled":false,"is_news":false,"display_username":"stas_agarkov","custom_reactions":"","journal_title":"","is_identity":false,"public_entries":[],"is_medius":false,"rkn_license":"","is_permanent":false,"is_community":false,"username":"stas_agarkov","is_journal_page":false,"is_bad_content":false,"is_suspended":false,"manifest":"{\"related_applications\":[{\"id\":\"com.livejournal.android\",\"platform\":\"play\"}],\"gcm_sender_id\":\"88462774281\",\"short_name\":\"stas_agarkov\",\"name\":\"stas_agarkov\",\"icons\":[{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj16.png\",\"type\":\"image/png\",\"sizes\":\"16x16\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj32.png\",\"type\":\"image/png\",\"sizes\":\"32x32\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj48.png\",\"type\":\"image/png\",\"sizes\":\"48x48\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj64.png\",\"type\":\"image/png\",\"sizes\":\"64x64\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj128.png\",\"type\":\"image/png\",\"sizes\":\"128x128\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj144.png\",\"type\":\"image/png\",\"sizes\":\"144x144\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj152.png\",\"type\":\"image/png\",\"sizes\":\"152x152\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj192.png\",\"type\":\"image/png\",\"sizes\":\"192x192\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj256.png\",\"type\":\"image/png\",\"sizes\":\"256x256\"},{\"src\":\"https://l-stat.livejournal.net/img/pwa_logo/lj512.png\",\"type\":\"image/png\",\"sizes\":\"512x512\"}],\"gcm_user_visible_only\":true,\"description\":\"stas_agarkov\",\"display\":\"standalone\",\"start_url\":\"https://stas-agarkov.livejournal.com?adaptive\",\"theme_color\":\"#004359\",\"background_color\":\"#004359\",\"prefer_related_applications\":false,\"id\":\"?pwa_id=10141809\"}","profile_url":"https://stas-agarkov.livejournal.com/profile/","is_memorial":false}; Site.entry = null; (function(){ var p = {"remote_is_identity":null,"remote_is_maintainer":0,"auth_token":"sessionless:1735174800:/__api/::292e96db7cb582901a2b1b1a31b961b9ebf537bd","locale":"en_US","remoteUser":null,"remote_is_sup":0,"remoteJournalBase":null,"statprefix":"https://l-stat.livejournal.net","vk_api_id":"2244371","ctx_popup":1,"jsonrpcprefix":"https://l-api.livejournal.com","siteroot":"https://www.livejournal.com","templates_update_time":900,"media_embed_enabled":1,"v":1735041763,"advc_token":"1735178543:a866e8279373efb6b12949058e393ab45a960574","currentEntryRecommendations":0,"currentLanguage":"en_LJ","server_time":1735177943,"logprefix":"","remote_email_reconfirmed":1,"counterprefix":"https://xc3.services.livejournal.com/ljcounter/","currentJournalBase":"https://stas-agarkov.livejournal.com","isCustomDomain":false,"isTrustedCustomDomain":false,"remoteLocation":{"city_id":"261415","city_rus_name":"","country_name":"United States","longitude":"-73.2637","region_code":"CT","region_name":"Connecticut","country_short":"US","latitude":"41.1412","city_name":"Fairfield"},"untrusted_ssl":["test.elecsnet.ru","www.arte.tv/en/","yourlisten.com","www.retromap.ru","flymeango.com/","www.mreporter.ru","epronto.ru","globalgallery.ru","verold.com","bbc.co.uk","travelads.ru","rutv.ru","prolivestream.ru","redigo.ru","gettyimages.com","beznomera.ru","videobasher.ru","maxkatz.ru","livesignal.ru","spring.me","www.music1.ru","podfm.ru","wikimapia.org","fashionmedia.tv","www.caissa.com","globalgallery.ru","turngallery.com","www.now.ru","pik-tv.com","mrctv.org","brainmaggot.org","promodj.com","jizo.ru","televidoc.ru","fidel.ru","so-l.ru","weclever.ru","rutv.ru","fotogid.info"],"fileprefix":"https://l-files.livejournal.net","likesprefix":"https://likes.services.livejournal.com/get","ljold":"","writers_block_community":"https://writersblock.livejournal.com/","country":"US","isBackendMobile":false,"inbox_update_poll":0,"flags":{"journal_v3":true,"branding_tretyakovgallery":true,"messages_v6":false,"meta":false,"tosagree_show":true,"friendsfeed_v3_settings":true,"rss_tour":true,"s1comment_preview":true,"medius":false,"fake_setting":true,"air_tour":true,"browse_lang_filter":true,"regionalrating_tour":false,"discovery":true,"add_friend_page_redesign":true,"manage_communities_v5":false,"lj_magazine_post_in_rating":false,"regional_ratings":true,"adaptive_lj_mobile":true,"quick_comment":true,"selfpromo_noc":false,"writers_block":false,"reactions_req":true,"medius_ui":true,"cosmos2021_ljtimes":true,"your_friends_block":true,"novogodia_banner":true,"friendsfeed_v3":true,"discovery_times_grants":true,"likes":true,"managenotes_v6":true,"meta_geo":true,"loginform_v8":true,"adv_adfox_ssp_mobile":true,"medius_reading_time_cards":true,"top_user_cards":true,"reactions_post":false,"notification_center":false,"your_choice_block":true,"ru_geo":false,"adv_loader":true,"commercial_promo_noc":false,"pocket":true,"lj_magazine_improvements":true,"img_comments":true,"reactions":true,"feed_promo_beta":false,"lena_comment_popup":true,"friendsfeed_tour":true,"lj_repost":false,"recaptcha":true,"image_magick_autobreak":true,"sherrypromo":false,"ljwelcomevideo":false,"video_update_tour":false,"move_billboard_to_scheme":true,"hashmobbanner":false,"medius_schemius":false,"contextualhover_v7":true,"homepage_v3":true,"rambler_adblock":true,"feed_promo":true,"three_posts_tour":true,"superban_step2":true,"photo_challenge_ny":true,"photo_v4":true,"hashmobbutton":false,"medius_sharings":true,"canva_geo":true,"post_2017_beta1":true,"auth_from_frame":false,"cosmos2021":true,"likes_display":true,"antiadblock":true,"shopius":false,"repost_facebook":true,"facebook_auth":true,"endless_scroll":true,"rec_sys_medius":true,"notification_center_display":false,"interactive_stripe":false},"rpc":{"domain":{"comment.add":1,"notifications.get_events_counter":1,"repost.get_status":1,"relations.can_add_friends":1,"user.set_prop":1,"relations.can_add_subscribers":1,"notifications.read_all_events":1,"comment.is_log_comment_ips":1,"notifications.get_events":1,"likes.get_likes":1,"repost.delete":1,"journal.emailreconfirm_set":1,"repost.create":1,"comment.set_contentflag":1,"notifications.unsubscribe":1,"memories.set":1,"likes.get_votes":1,"journal.set_prop":1,"memories.remove":1,"relations.addfriend":1,"user.emailreconfirm_set":1,"journal.get_prop":1,"user.get_prop":1,"notifications.delete_event":1,"relations.removefriend":1,"comment.is_need_captcha":1,"repost.get_communities":1,"event.set_contentflag":1,"memories.get":1,"notifications.read_event":1,"entry.set_contentflag":1,"likes.vote":1,"likes.create":1},"ssl":{"journal.login":1,"signup.check_password":1,"signup.convert_identity_lite":1,"support.create_request":1,"signup.create_user":1,"signup.convert_identity":1,"user.login":1},"public":{"medius.top_user_cards_choice":"300","comment.get_thread":"900","latest.get_entries":"180","browse.get_posts":"300","gifts.get_gifts_categories":"60","gifts.get_all_gifts":"60","homepage.get_categories":"60","medius.asap":"300","medius.activities":"300","sitemessage.get_message":"3600","ratings.journals_top":"300","medius.get_public_items":"300","post.get_minipage_widget_counter":"60","browse.get_categories":"300","medius.get_homepage_items":"300","writers_block.get_list":"60","medius.top_user_cards":"300","medius.collection_items":"300","categories.get_public_category_posts":"60","medius.get_public_items_categories":"300","homepage.cool_pool":"300","browse.get_communities":"300","homepage.get_search_hints":"300","homepage.get_rating":"300"}},"should_show_survey":false,"pushwoosh_app_id":"28B00-BD1E0","has_remote":0,"picsUploadDomain":"up.pics.livejournal.com","remoteLocale":"en_US","notifprefix":"https://notif.services.livejournal.com/","remote_is_suspended":0,"imgprefix":"https://l-stat.livejournal.net/img","remote_can_track_threads":null,"currentJournal":"stas_agarkov","esn_async":1,"currentEntry":"","pics_production":""}, i; for (i in p) Site[i] = p[i]; })(); Site.current_journal = {"url_profile":"https://stas-agarkov.livejournal.com/profile/","userid":10141809,"journaltype":"P","is_comm":"","is_syndicated":"","userpic_h":100,"is_person":1,"badge":null,"is_mediapartner":"","is_paid":0,"display_username":"stas_agarkov","url_journal":"https://stas-agarkov.livejournal.com","is_identity":"","is_shared":"","display_name":"stas_agarkov","username":"stas_agarkov","userpic_w":100,"can_receive_vgifts":1,"url_allpics":"https://www.livejournal.com/allpics.bml?user=stas_agarkov","url_userpic":"https://l-userpic.livejournal.com/58364245/10141809"}; Site.version = '809'; </script> <script type="text/javascript" src="https://l-stat.livejournal.net/js/??.ljlib.js?v=1735041763"></script> <script type="text/javascript" src="https://l-stat.livejournal.net/js/??old/ljmobile.js?v=1735041763"></script> <!--[if gte IE 9]><script type="text/javascript" src="https://l-stat.livejournal.net/js/??deprecated/ie9pinned.js?v=1735041763"></script><![endif]--> <div class="footer-counters"> <!-- tns-counter.ru --> <script language="JavaScript" type="text/javascript"> (new Image()).src = '//www.tns-counter.ru/V13a***R>' + document.referrer.replace(/\*/g,'%2a') + '*sup_ru/ru/CP1251/tmsec=lj_mob/146290'; </script> <noscript> <img src="//www.tns-counter.ru/V13a****sup_ru/ru/CP1251/tmsec=lj_mob/146290" width="1" height="1" alt="" /> </noscript> <!--/ tns-counter.ru --> <!-- Yandex.Metrika counter --> <script type="text/javascript"> (function (d, w, c) { (w[c] = w[c] || []).push(function() { try { w.yaCounter27737346 = new Ya.Metrika({id:27737346, clickmap:true, trackLinks:true, accurateTrackBounce:true}); } catch(e) { } }); var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () { n.parentNode.insertBefore(s, n); }; s.type = "text/javascript"; s.async = true; s.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js"; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "yandex_metrika_callbacks"); </script> <noscript><div><img src="//cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter --> </div> </div> <div class="footer-copy b-strip">© 1999-2024 LiveJournal, Inc.</div> </div> <!--script type="text/ecmascript" src="/es"></script--> </div> </body> </html>