Содержание


Что нового в Java Portlet Specification V2.0 (JSR 286)?

Comments

Портлеты – это компоненты приложений с пользовательским интерфейсом, формирующие содержимое в одном из стандартных форматов разметки (к примеру, HTML). Эта разметка предназначена для агрегирования портлетов с другими фрагментами в единое большое приложение, например, страницу портала, как показано на рис. 1

Рис. 1. Пример страницы портала
Рис. 1. Пример страницы портала
Рис. 1. Пример страницы портала

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

Портлеты Java стали популярны после выхода первой версии Java Portlet Specification, JSR-168, выпущенной в 2003 сообществом Java Community Processes. С этого момента почти все вендоры, разрабатывающие порталы Java, как коммерческие, так и с открытым исходным кодом, реализовали этот стандарт, и разработчики стали писать портлеты, использующие Java Portlet API.

JSR 168, однако, остановилась на определении компонентной модели графического интерфейса и никак не определяла аспектов интеграции этих компонентов в приложения. Это ограничение, как и многие другие, не попавшие в версию 1.0 из-за нехватки времени, теперь преодолено в версию V2.0.

Работа над JSR 286 стартовала в январе 2006 года, финальная версия была закончена в феврале 2008. Экспертная группа JSR 286 включала всех значимых разработчиков порталов, как коммерческих, так и с открытым кодом, разработчиков средств интеграции портлетов и разработчиков сред разработки портлетов. Полный список членов экспертной группы можно посмотреть здесь.

Данная статья представляет собой обзор наиболее значимых новшеств JSR 286, а также включает некоторые примеры использования этих нововведений. Статья предполагает базовые знания программной модели портлетов, определенной в версии 1.0; вводная информация по JSR168 доступна здесь.

Создание составного приложения, использующего взаимодействие портлетов

Основные новинки версии 2.0 предоставляют возможность организации взаимодействия между различными портлетами, которые могут быть реализованы разными сторонами и упакованы в разные WAR-файлы. Координационные возможности JSR 286 основаны на модели слабой связи вида "публикатор-подписчик", которая не требует детальных знаний портлетов о реализации друг друга. Во время разработки вы просто определяете данные, которые понимает ваш портлет, а собственно соединения создаются во время развертывания или запуска. Пользуясь этими коммуникационными возможностями администраторы портала или бизнес-пользователи теперь могут строить более сложные и масштабные приложения из портлетов-компонентов без всякого программирования. Данная функциональность открывает возможности построения новой функциональности путем комбинирования существующих компонентов в комбинированные бизнес-приложения аналогично популярным приложениям Web 2.0 – даже таких, о которых авторы компонентов даже и не думали.

JSR 286 определяет два способа взаимодействия, предназначенных для разных случаев использования:

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

Мы подробнее объясним оба способа ниже.

События

JSR 286 позволяет портлетам посылать и получать сообщения. Как упоминалось выше, используется модель слабой связи, в которой портальное приложение действует как брокер между различными портлетами и распределяет события.

Рис. 2. Доставка сообщения от портлета 1 к портлетам 2 и 3
Рис. 2. Доставка сообщения от портлета 1 к портлетам 2 и 3
Рис. 2. Доставка сообщения от портлета 1 к портлетам 2 и 3

На рис. 2 подробно показано, как работает распределение событий. Портлет 1 в своем дескрипторе развертывания portlet.xml определяет, что он может публиковать некое событие A. Портлеты 2 и 3 в своих дескрипторах portlet.xml определяют, что они могут получать событие A. Теперь администратор портала или бизнес-пользователь объединяют их на одной странице и создают соединения между отправителем-портлетом 1 и получателями-портлетами 2 и 3. С этого момента пользователь работает с портлетом 1 и выполняет в нем какие-то действия. В результате этого портлет генерирует событие A. Как только портлет послал событие, компонент-брокер (обычно являющийся частью портала) ищет получателей события. В нашем примере получателей определяют связи между портлетом 1 и портлетами 2 и 3 для этого события. Таким образом, брокер событий вызывает в портлетах 2 и 3 метод жизненного цикла processEvent, определенный в версии 2.0 спецификации, с событием A в качестве параметра. После обработки события стартует процесс перерисовки страницы, и портлеты могут поменять свой вид в зависимости от изменения состояния, вызванного обработкой события и выполненными им действиями.

JSR 286 не специфицирует, как определяются и управляются соединения между портлетами. Обычно соединения определяются во время создания страницы или автоматически находятся порталом во время исполнения приложения. Первый путь предоставляет большую гибкость и управляемость, в то время как второй путь проще реализовать. В обоих случаях порталу требуется информация о том, какого типа события портлет может получать и отправлять, поэтому эту информацию надо предоставлять в дескрипторе развертывания portlet.xml. Как это делается? Во-первых, вы предоставляете общее определение события, а затем ссылаетесь на это определение в секции описания портлета с тегами, указывающими, что портлет может получать или посылать такое событие. Каждое событие уникально идентифицируется с помощью XML-квалифицированного имени (QName). QName - механизм уникальных XML-имен; он также имеет Java-представление в классе стандартной Java-библиотеки javax.xml.namespace.QName. QName состоит из пространства имен (например, http://www.ibm.com) и локальной части (например, myEvent).

В Листинге 1 приведен пример.

Листинг 1. Пример определения публикуемого события
	<portlet>
		…
		<supported-publishing-event>
			<qname xmlns:x="http://com.ibm/portal/portlets/ns">
x:city</qname>
		</supported-publishing-event>
	</portlet>
	<event-definition>
		<qname xmlns:x="http://com.ibm/portal/portlets/ns">x:city</qname>
		<value-type>java.lang.String</value-type>
	</event-definition>

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

Кроме имени, надо указать класс параметра события, чтобы портальное приложение сериализовало / десериализовало параметр в случае, если портлеты используют различные загрузчики классов или даже запущены как удаленные портлеты, используя технологию Web Services for Remote Portlets (WSRP). Поэтому тип параметра события должен или быть одним из простых типов (например, String), или поддерживать сериализацию Java и сериализацию Java Architecture for XML Binding (JAXB). Почему нам надо использовать дополнительную JAXB-сериализацию? Портлет может пожелать послать событие удаленному портлету, запущенному с использованием WSRP. Такие удаленные портлеты могут быть вообще написаны не на Java, и поэтому нужен более общий механизм, чем стандартная сериализация Java. JAXB поддерживает сериализацию объектов Java в XML; все, что вам придется сделать - соответственно аннотировать ваш Java-объект.

На этом тонкости заканчиваются; дальше все будет проще: получение и посылка сообщений вашим портлетом. Вы можете отсылать события, вызывая метод setEvent в ActionResponse или EventResponse. Для получения событий вам надо наследовать портлет от GenericPortlet либо реализовать интерфейс EventPortlet самостоятельно. Расширение класса GenericPortlet имеет дополнительное преимущество - оно дает возможность использовать аннотации для указания специальных методов обработки событий, действий, а также отрисовки специфических режимов отображения.

В Листинге 2 приведен пример.

Листинг 2. Пример обработки события с использованием аннотаций
	@ProcessEvent(qname="{http://com.ibm/portal/portlets/ns}city")
	public void cityEvent(EventRequest request, EventResponse response )
                                      throws IOException, PortletException 
	{	
		Event ev = request.getEvent();
		if ( ev.getValue().equals("Orlando") ) {
			….
		}
		…
	}

Публикуемые параметры отображения

Публикуемые параметры отображения позволяют совместно использовать параметры запроса в различных портлетах или других артефактах, подобно темам или портальным страницам в IBM WebSphere Portal. Определение публикуемых параметров отображения очень похоже на определение события: вы указываете уникальное QName и - при желании - псевдонимы в дескрипторе развертывания portlet.xml. QName и псевдонимы используются для объединения публикуемых параметров отображения различных портлетов. Эти публикуемые параметры можно представлять себе как общую область хранения, с помощью которой все участвующие портлеты могут получать и устанавливать значения переменных.

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

На рис. 3 показан пример, в котором навигационный портлет и портлет содержимого совместно используют публикуемый параметр docid. После того, как вы сделали выбор в навигационном портлете, он устанавливает docid в "document3", а портлет содержимого отображает содержимое этого документа, поскольку его состояние отображения (ID текущего документа) было изменено внешним воздействием. Отметьте, что публикуемые параметры отображения могут храниться в URL, как в реализации WebSphere Portal, то есть вы можете хранить закладки и использовать кнопки браузера Вперед и Назад.

Как портлет получает доступ к этим публикуемым параметрам? В самом портлете вы можете получать публикуемые параметры отображения через те же самые методы, которые используются для обработки обычных параметров отображения в портлетах версии 1.0. Определено, что эти методы принимают только String в качестве ключей, и вам не надо определять QNames как идентификаторы в portlet.xml, чтобы использовать их для адресации публикуемых параметров внутри кода портлета.

В большинстве случаев код портлета может не меняться, независимо от типа публикуемых параметров - public или private, и вы можете добавить взаимодействие с вашим портлетом простым декларированием этих составляющих состояния отображения в portlet.xml, который впоследствии может быть сконфигурирован из внешнего источника данных для их публикации. Если же вы хотите дать коду доступ только к публикуемым параметрам отображения, можно также использовать метод запроса getPublicParameterMap.

Использование ресурсов

В первой версии Java Portlet Specification нельзя было использовать динамически генерируемые ресурсы напрямую из кода портлета. Вместо этого приходилось создавать дополнительный сервлет, который обслуживал запрос к ресурсам. Это ограничение имело определенные недостатки:

  • У вас не было доступа к контексту портлета. Соответственно, параметры отображения, режим портлета, состояние окна отрисовки, настройки портлета и объект сессии портлета были недоступны.
  • URL, созданные в сервлете, находились за пределами видимости портлета и в итоге часть преимуществ использования портлетов терялась. Кроме того, текущее состояние портальной страницы, сосредоточенное в URL портала, также теряло значение.
  • Сервлет запускался независимо от прав доступа, выданных в портале, и требовал установки собственных ограничений безопасности.

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

Спецификация версии 2.0 предусматривает новый тип URL – ресурсные URL. Ресурсные URL обрабатываются в методе serveResource, определенном в интерфейсе ResourceServingPortlet, который можно использовать для создания динамически генерируемых ресурсов непосредственно из портлета.

Ресурсные URL

Ресурсный URL необходим для запуска нового метода жизненного цикла serveResource. Давайте начнем с изучения деталей этого нового типа URL.

Ресурсные URL можно создавать подобно остальным портлетным URL, методом createResourceURL объектов RenderResponse и ResourceResponse, к примеру:

ResourceURL url = response.createResourceURL();

Теперь вы можете указать в URL параметры, как это делается для других портлетных URL. Вы получите эти параметры при вызове serveResource. Заметьте, что ResourceURL не может задавать новые параметры отображения, режим портлета или состояние окна. Это ограничение возникло из-за того, что вызов serveResource не создает полноценной новой страницы портала, а возвращает ответ метода serveResource. То есть портал не имеет возможности изменить части страницы, в которых может содержится информация; например, в WebSphere Portal все URL содержат такую информацию и поэтому требуют обновления.

Вы также можете задать в ресурсном URL дополнительный ID ресурса, который однозначно идентифицирует ваш ресурс. Если вы расширяете GenericPortlet, GenericPortlet пробует перенаправить запрос по данному ID ресурса в метод serveResource. Вы можете указать путь к ресурсу в виде ID, как показано ниже:

url.setResourceID("WEB-INF/jsp/xmlcontent.jspx");

В этом случае GenericPortlet автоматически перенаправит вызов указанной JSP, которая может использовать информацию о состоянии портлета путем подключения библиотеки портлетных тегов. Заметьте, что статические ресурсы, типа файлов GIF, упакованные в ваш портлетный архив WAR, лучше использовать с помощью URL статических ресурсов:

String url = response.encodeURL(request.getContextPath()+"/icons/myigif.gif");

Обслуживание запросов к статическим ресурсам с использованием метода serveResource вызывает излишнюю нагрузку.

Обслуживание запросов к ресурсам

Чтобы инициировать метод serveResource по URL ресурса, вам надо либо реализовать новый ResourceServingPortlet, либо расширить GenericPortlet, который уже реализует нужный интерфейс. При вызове serveResource вы получаете пару объектов запрос/ответ.

Объект запроса ресурса содержит методы, схожие с объектом запроса портлета, но дополнительно он позволяет вам получить доступ к подгружаемым на сервер данным. В объекте запроса ресурса обработчик параметров предоставляет доступ к трем различным типам параметров:

  • Параметры ресурсов, установленные в ресурсном URL, доступны только при запросе ресурса, получить их можно через getPrivateParameterMap
  • Приватные параметры отображения портлета доступны через getPrivateRenderParameterMap
  • Публичные параметры отображения доступны через getPublicParameterMap call

Обычные методы доступа к параметрам возвращают объединенный набор из этих трех коллекций. Параметры, имеющие один и тот же ключ, объединяются - сначала значение параметра ресурса, а затем значение либо приватного, либо публичного параметра отображения - в зависимости от того, определен ли параметр как публичный в portlet.xml.

С ресурсными URL можно использовать все методы протокола HTTP, а не только один GET, как при отрисовке содержимого. То есть вы можете применять такие методы, как POST или DELETE, для изменения состояния ресурса при вызове метода serveResource. Эти изменения состояния, однако, должны быть ограничены только внутренними данными портлета - настройками и атрибутами сессии. Нежелательно модифицировать данные, имеющие отношение к другим портлетам, поскольку портал не имеет возможности обновить другие составляющие страницы после вызова serveResource, и изменения на уровне страницы могут остаться невидимыми.

Примечание: Параметры отображения, режим работы портлета и состояние окна также подпадают под эту категорию в некоторых реализациях, таких как WebSphere Portal, хранящих информацию в URL для обеспечения возможностей использования закладок и кнопок «Назад» и «Вперед» браузера. При такой реализации изменение параметров отображения, к примеру, требует изменения всех URL страницы. Портал не может этого сделать, поскольку ответ портлета клиенту формируется непосредственно портлетом.

Поскольку данные, возвращаемые методом serveResource, не объединяются с разметкой, формируемой собственно порталом, поток ответа на запрос ресурса полностью контролируется самим портлетом. Например, портлет может сам установить код статуса HTTP.

Уровни кэширования ресурсов

Существует множество вариантов предоставления доступа к ресурсам; соответственно, существуют различные требования к возможностям кэширования результатов вызова serveResource. Вы можете влиять на поведение системы кэширования HTTP с помощью метода setCacheability в ResourceURL. Данный метод указывает порталу, какие параметры требуются для вызова serveResource. Портал может удалять ненужные данные из URL, что приводит к уменьшению числа вариантов URL и увеличивает эффективность HTTP-кэширования. Заметьте, что настройки кэшируемости ресурса имеют значение только если вы разрешаете кэшировать ответ путем добавления соответствующего HTTP-заголовка в возвращаемом ответе. Это значит, что вам надо как минимум установить время истечения актуальности с использованием response.getCacheControl().setExpirationTime(). Если содержимое ответа не специфично для каждого пользователя, рекомендуется указывать кэш публичной области видимости.

JSR 286 поддерживает следующие варианты использования кэширования:

  • Полностью кэшируемые ресурсы, которые не зависят от состояния. В качестве примера можно привести программно генерируемые страницы SVG, зависящие только от настроек портлета. Для пометки результата serveResource как полностью кэшируемого вам надо установить кэшируемость в ресурсном URL в FULL. Эта настройка означает, что портал может генерировать URL, не содержащие никаких данных о состоянии взаимодействия со страницей и портлетами на странице. Таким образом, браузер может кэшировать результаты запроса serveResource как минимум на время нахождения пользователя на текущей странице.

    В результате отсутствия информации о состоянии страницы и портлетов на ней вы не можете получать текущие параметры отображения, данные о режиме портлета или состоянии окна в вашем портлете. Имейте в виду, что из-за отсутствия информации о состоянии на результат, возвращаемый serveResource, накладываются следующие ограничения: вы можете включать только полностью кэшируемые ресурсные URL, но не URL действий или отображений.

  • Полностью кэшируемые общие ресурсы, такие как статические ресурсы, к примеру - библиотеки JavaScript. Вы можете указать полную кэшируемость ресурса (FULL) и пометить его как общий, добавив дополнительное свойство SHARED в URL, с именем ресурса в качестве значения. Имя должно быть уникальным идентификатором QName, например {http://dojotoolkit.org/v1.0}dojo.js.

    Задание общего свойства позволяет порталу использовать единственную версию общего ресурса на страницу. Однако при этом по-прежнему требуется, чтобы портлет содержал такие ресурсы в своем архиве WAR на случай, если они еще не загружены в хранилище портала.

    Использование ресурсов портала, не создаваемых портлетами, зависит от реализации конкретного поставщика; например, в WebSphere Portal вы имеете возможность использовать ResourceURLAccessor для создания ресурсных URL.

  • Кэшируемые на уровне портлета ресурсы, зависящие от состояния портлета, к примеру - динамически генерируемые PDF-файлы. Если вам надо получить доступ к состоянию портлета, например, параметру отображения, режиму портлета или состоянию окна, но вы не собираетесь добавлять URL отображения в ваш ответ - установите уровень кэширования в PORTLET. Этот уровень допускает изменение ресурса при каждом взаимодействии клиента с портлетом, и, кроме того, поддерживает использование кэшированного ресурса при взаимодействии портлетов на странице.
  • Кэшируемые на уровне страницы ресурсы, такие как ресурсы, поддерживающие URL отображения, к примеру - ответы с разметкой при вызове Ajax. Кэширование на уровне PAGE используется по умолчанию. У вас нет никаких ограничений в использовании serveResource, и это значит, что вы можете генерировать ссылки для различных действий. Также это означает, что ваш ресурс не может быть закэширован для нескольких взаимодействий с портальной страницей в случае, если менялась хотя бы часть ее содержимого.

Подводя итоги, эти уровни кэширования предоставляют вам максимально широкие возможности для указания вариантов кэширования для времени исполнения. Во время исполнения вы также можете увеличивать кэшируемость ресурсов другими путями, например, отслеживая изменения состояния клиентом и перерисовывая только ту часть, которая действительно в этом нуждается. В качестве примера можно посмотреть тему портала "Client-side Aggregation" на WebSphere Portal V6.1 Beta.

Использование Ajax

В версии 1.0 поддержка использования Ajax была минимальной: у вас была только возможность предоставить порталу дополнительный сервлет, который генерировал фрагменты ответа на Ajax-запросы. Такой сервлет, однако, мог быть адресован исключительно напрямую, в обход среды портала, и он не поддерживал ни учет состояния портлета, ни стандартные портальные ограничения доступа, как вы видно на рис. 4. Единственным вариантом доступа к данным о состоянии портлета в сервлете было использование сессии приложения или параметров URL, передаваемых при вызове сервлета.

Рис.4. Поддержка Ajax в JSR 168: обслуживание Ajax-запроса с использованием дополнительного сервлета
Рис.4. Поддержка Ajax в JSR 168: обслуживание Ajax-запроса с использованием дополнительного сервлета
Рис.4. Поддержка Ajax в JSR 168: обслуживание Ajax-запроса с использованием дополнительного сервлета

Каким образом новая спецификация поддерживает использование Ajax?

Как уже описывалось ранее, JSR 286 предоставляет возможность формирования ресурса напрямую из вашего портлета. Следовательно, вы можете направить XmlHttpRequests в ResourceURL и на стороне сервера получить полный доступ к контексту портлета - параметрам отображения, режиму работы, состоянию окна, настройкам портлета и портлетной сессии.

Мы также узнали, как можно менять состояние при вызове serveResource:

  • Изменяя настройки портлета
  • Изменяя данные в портлетной сессии

Теперь вы можете использовать дополнительные возможности Web - например, асинхронное обновление данных, дающее возможность создавать удобный интерфейс пользователя.

Рис. 5. Поддержка Ajax в JSR 286: обслуживание запроса Ajax напрямую портлетом
Рис. 5. Поддержка Ajax в JSR 286: обслуживание запроса Ajax напрямую портлетом
Рис. 5. Поддержка Ajax в JSR 286: обслуживание запроса Ajax напрямую портлетом

На рис. 5 показано решение проблемы использования Ajax в JSR 286. Вы можете видеть, что запрос Ajax проходит непосредственно через портальный сервлет и находится под контролем портала. Вы также можете использовать ресурсные URL для формирования ссылок на ваши библиотеки JavaScript.

Уменьшение расхождений с программной моделью сервлетов

Первая версия Java Portlet Specification в некоторых областях ограничивала портлетную программную модель в сравнении с программной моделью сервлетов. Это ограничение было введено из-за того, что портлеты собираются в страницы портала, и потому все концепции, предполагающие использование только компонента страницы трудно реализовать в модели программирования портлета.

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

Cookies, секция заголовков документа и заголовки HTTP

В первой версии Java Portlet Specification портлет не мог воздействовать на содержимое страницы вне своей портлетной секции. Во второй версии вы можете устанавливать cookies, элементы заголовка документа (например, HTML-элементы meta, link, style) и HTTP-заголовки (такие как специфичные для приложения заголовки Pragma).

Двухфазный жизненный цикл отображения
Для обхода проблем версии 1.0, связанных с формированием содержимого вне окна портлета, в версию 2.0 был добавлен двухфазный процесс отображения:

  1. Фаза RENDER_HEADER, который позволяет портлету вернуть содержимое, не связанное непосредственно с портлетной областью - заголовок портлета, предпочитаемые режимы портлета, cookies, заголовки документа, HTTP-заголовки.
  2. Фаза RENDER_MARKUP, которая позволяет портлету вернуть непосредственно его обычную разметку.

Эти две фазы нужны для некоторых реализаций портальной модели, например, WebSphere Portal. Поток формирования страницы и содержимого портлета отправляется напрямую на клиента, чтобы избежать затрат на буферизацию. В таком случае отображение портлета не может повлиять на содержимое секции заголовков документа, поскольку она уже записана в поток. Таким образом, если портлету требуется предоставить возможность добавлять собственные записи в секцию заголовков или сформировать заголовок портлета, необходимо включить поддержку двухфазной отрисовки портлета в дескрипторе развертывания портлета, как показано в Листинге 3.

Листинг 3. Включение поддержки двухфазного жизненного цикла отображения
<portlet>
…
		<container-runtime-option>
			<name>javax.portlet.renderHeaders</name>
			<value>true</value>
		</container-runtime-option>
	</portlet>

Двухфазное отображение портлета легко использовать, если вы расширяете GenericPortlet и переопределяете предоставленные им методы, такие как getTitle и getHeaders. Класс GenericPortlet сделает все остальное за вас.

Использование cookies
Вы можете добавлять собственные cookies в ответ любого из методов жизненного цикла портлета (processAction, processEvent, render, serveResource) с помощью приведенного ниже кода:

response.addProperty(javax.servlet.http.Cookie cookie)

Добавленный cookie теперь можно получить в любом методе жизненного цикла, используя:

request.getCookies()

Использование cookies в портлетах отличается от использования cookies в сервлетах следующими аспектами:

  • Как упоминалось, для установки cookies в методе отображения надо включить опцию renderHeaders, а cookies устанавливать на фазе RENDER_HEADERS, например - в переопределенном методе doHeaders() класса GenericPortlet.
  • Cookies могут быть недоступны на клиентской стороне по разным причинам - например, если они сохранены на портальном сервере или помещаются в другое пространство имен (когда портлет запущен как удаленный через протокол WSRP).
  • Не гарантируется возможность совместного использования сookies разными портлетами.

Действия include и forward в диспетчере запросов

В первой версии Java Portlet Specification была только возможность включения сервлетов или JSP из методов жизненного цикла отображения. Вторая версия позволяет использовать также forward и полноценный include из всех методов жизненного цикла.

Это дополнение спецификации предоставляет возможность перенаправлять поток действий или событий в обработчики, написанные в формате сервлетов, а также в обработчик запросов ресурса по идентификатору ресурса, установленному в ResourceURL.

Использование слушателей жизненного цикла сервлета

В версии 1.0 стандартные сервлетные слушатели HttpSession также могли использоваться для получения событий объекта PortletSession, так как PortletSession – это фасад поверх HttpSession. Версия 2.0 расширяет список слушателей жизненного цикла, определенный в Java Servlet Specification V2.5, и позволяет усовершенствовать взаимодействие между портлетными и сервлетными объектами:

  • javax.servlet.ServletContextListener. Для извещений о событиях контекста сервлета или соответствующего ему контекста портлета.
  • javax.servlet.ServletContextAttributeListener. Для извещений об изменениях атрибутов контекста сервлета или соответствующего ему контекста портлета.
  • javax.servlet.http.HttpSessionActivationListener. Для извещений об активации или пассивации HTTPSession или соответствующей PortletSession.
  • javax.servlet.http.HttpSessionAttributeListener. Для извещений об изменениях атрибутов в HTTPSession или соответствующей ей PortletSession.
  • javax.servlet.http.HttpSessionBindingListener. Для извещений о привязке объекта к HTTPSession или к соответствующей PortletSession.
  • javax.servlet.ServletRequestListener. Для извещений об изменениях в HTTPServletRequest или использующем его запросе портлета в текущем портальном приложении.
  • javax.servlet.ServletRequestAttributeEvent. Для извещений об изменениях атрибутов HTTPServletRequest или использующем его запросе портлета в текущем Web-приложении.

Слушатель запроса сервлета может отличить простой сервлетный запрос, относящийся к сервлету, от сервлетного запроса, направляемого в портлет, просматривая содержимое атрибута запроса "javax.portlet.lifecycle_phase". Этот атрибут устанавливается в запросе, направляемом в портлет, и отображает текущую жизненную фазу запроса портлета.

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

Расширения JSR 286

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

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

Некоторые точки расширения требуют соответствующей поддержки от контейнера. Это, в частности:

  • Свойства запроса/ответа из версии 1.0 для установки расширенных свойства запросу или ответу. Версия 2.0 добавляет несколько новых расширенных свойств, в частности параметры кэширования портлета.
  • Свойства PortletURL для расширения PortletURL, к примеру - предопределенное свойство SHARED для ResourceURL.
  • Опции времени исполнения контейнера для управления дополнительными возможностями поведения контейнера (например, двухфазным режимом отображения).

Давайте подробнее рассмотрим новые возможности расширения Java Portlet Specification в следующем разделе.

Фильтры портлетов

Новые фильтры портлетов позволяют вам добавлять собственные фильтры к каждому вызову метода жизненного цикла портлета. Следуя общим шаблонам декорирования, фильтры могут выполнять пре- и постобработку, а также модифицировать или оборачивать объекты запроса и ответа, передаваемые в портлет. Типичное использование фильтров портлетов описано ниже:

  • Передача портлету информации из дополнительных источников путем изменения/добавления атрибутов или параметров
  • Фильтрация выводимого содержимого в соответствии с заданными параметрами безопасности или совместимости разметки
  • Сбор диагностической информации
  • Взаимодействие между разными библиотеками Web-приложений

WebSphere Portal V6.1, к примеру, использует функционал фильтров для добавления в содержимое портлетов дополнительной семантической информации, используемой на клиентской стороне для функционала "действие по щелчку мыши".

Программная модель портлетных фильтров построена на основе стандартной фильтрации сервлетов:

  1. Определение фильтров в дескрипторе развертывания. Это определение делается с использованием элемента <filter>, в котором вам также надо указать, к какому именно методу жизненного цикла применять фильтр.
  2. Реализация соответствующего интерфейса Filter в вашем собственном классе фильтра. Вы можете перечислить несколько методов жизненного цикла и реализовать несколько интерфейсов Filter в вашем классе.
  3. Предоставление элемента filter-mapping, в котором вы можете описать, какие именно портлеты будут применять ваш фильтр (разрешается использовать символ "*" для указания списка портлетов в виде шаблона, например - если надо использовать фильтр для всех портлетов в приложении).

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

Листинг 4. Пример определения фильтров
// определение фильтра
<filter>
		<filter-name>PortletFilter</filter-name>
		<filter-class>com.example.PortletFilter</filter-class>
		<lifecycle>RENDER</lifecycle>
</filter>

// использование фильтра
<filter-mapping>
		<filter-name>PortletFilter</filter-name>
		<portlet-name>MyPortlet</portlet-name>
</filter-mapping>

Во время исполнения к портлету применяется вся цепочка фильтров. Каждый фильтр получает текущие объекты запроса и ответа, или их обернутые варианты, созданные предыдущим фильтром, а также объект цепочки фильтров. После выполнения предобработки фильтр может либо прервать цепочку фильтрации, либо вызвать следующий по списку фильтр, передав ему объекты запроса/ответа или их обернутые версии. Последним элементом цепи фильтров является собственно портлет.

В листинге 5 приведен пример фильтра, выполняющего некоторую пред- и постобработку и предоставляющий портлету обернутый объект запроса.

Листинг 5. Пример реализации фильтра
public class PortletFilter implements RenderFilter {

  		public void doFilter(RenderRequest req, 
         RenderResponse resp, FilterChain chain) throws .. {
			PrintWriter pw = resp.getWriter();
			pw.write("Pre-processing");

			MyRenderResponseWrapper resWrapper = 
					new MyRenderResponseWrapper(res);
			chain.doFilter(req, resWraper);

			pw.write("Post-processing");
  		}
}

Заметьте - когда вы используете классы-обертки, вы должны следовать шаблону wrapper и изменять только существующее поведение. Не добавляйте новых методов в оборачивающий класс; в цепочке могут быть другие обертки, о которых вы ничего не знаете, и портлет будет не в состоянии получить доступ к новым методам, добавленным вами. Если же вы хотите предоставить портлету дополнительные возможности, то передайте объект доступа к вашим данным в качестве атрибута запроса.

Слушатели портлетного URL

В некоторых случаях вы можете пожелать централизованно управлять специфическими свойствами портлетных URL или изменить процесс создания портлетных URL в существующих портлетах. Пример также включает реализацию отображения ресурсов на общие идентификаторы. Вы реализуете это отображение однократно, в слушателе, а слушатель проверяет, является ли ресурсный URL ссылкой на ресурс, для которого определено общее QName, и после этого устанавливает свойство SHARED у соответствующего QName. Для таких случаев JSR 286 предусматривает возможность использовать специальные слушатели портлетных URL.

Такой слушатель надо зарегистрировать в элементе listener в дескрипторе развертывания портала, а ваш класс должен реализовать интерфейс PortalURLGenerationListener, который определяет метод обратного вызова для каждого типа портальных URL: действие, отображение и ресурс.

Управляемые портлетом режимы

В JSR 168 портлеты могли пользоваться только теми режимами, которые поддерживались реализацией портала. В некоторых случаях портлетам желательно реализовать дополнительную функциональность, с тем же внешним видом, как и у стандартных режимов, поддерживаемых порталом (допустим, с контекстными меню в портлетном окне). Например, портлетный режим ShowShoppingCart показывает список всех позиций, которые есть в вашей карте покупателя.

Для поддержки подобных вариантов использования JSR 286 вводит управляемые портлетом режимы, которые не определяются в портале, а управляются непосредственно портлетом. Портлет может декларировать режим работы в дескрипторе развертывания портлета, как показано в листинге 6.

Листинг 6. Определение управляемого портлетом режима работы
 	<custom-portlet-mode>
   		<description>Show shopping cart</description>
<portlet-mode>ShowShoppingCart</portlet-mode>
		 <portal-managed>false</portal-managed>
  	</custom-portlet-mode>

Важный аспект приведенного выше листинга - установка режима portal-managed в false. Эта настройка сообщает порталу, что режим работы должен соответствовать стандартному режиму View с сохранением всех аспектов, включая предоставление портлета с предпочтительными настройками и доступом. Портал должен предоставлять графические элементы управления, которые позволят портлету переключиться в этот режим. Портлет может определять локализованные ресурсы для отображения, используя секцию группы ресурсов:

javax.portlet.app.custom-portlet-mode.<name>.decoration-name.

Поскольку портал на самом деле не знает семантики управляемых портлетом режимов, он также не знает, когда имеет смысл переключаться в портлетно-управляемый режим, а когда - нет. Соответственно спецификация JSR 286 предоставляет вам возможность указывать порталу, в каком портлетном режиме должны работать графические элементы управления. Эта информация передается в объекте ответа отображения портлета и устанавливается методом setNextPossiblePortletModes. Используя GenericPortlet, вы можете просто переопределить getNextPossiblePortletModes, а GenericPortlet позаботится о правильной установке соответствующих переменных в секции RENDER_HEADER. Заметьте, что вам надо устанавливать список режимов в каждом ответе, также вам требуется включить опцию контейнера портлетов javax.portlet.renderHeaders, подробно описанную выше.

Опции времени выполнения контейнера

Опции времени выполнения контейнера портлетов предоставляют портлету возможность изменять поведение, определенное в Java Portlet Specification или добавлять дополнительные варианты поведения. Мы уже видели пример подобных настроек - опцию renderHeaders. JSR 286 определяет список настроек времени исполнения, все они необязательны, за исключением actionScopedRequestAttributes. Кроме этих настроек реализация портала может добавлять свои собственные специфические опции.

В листинге 7 показано, как можно установить опции времени исполнения с помощью дескриптора развертывания портлета.

Листинг 7. Шаблон установки опций времени исполнения контейнера
<portlet>
…
		<container-runtime-option>
			<name>ИМЯ_НАСТРОЙКИ</name>
			<value>Вы можете установить один или более параметр</value>
		</container-runtime-option>
	</portlet>

Данные опции определены в JSR 286:

  • javax.portlet.escapeXml. Позволяет отключать экранирование XML в портлетных URL, включенное по умолчанию, в случае, если вы используете портлеты спецификации JSR 168, которые предполагают, что URL содержат неэкранированный XML. Это требуется при миграции портлетов в контейнер, работающий в соответствии со спецификацией JSR 286.
  • javax.portlet.renderHeaders. Включает двухфазный механизм отображения, давая вам возможность устанавливать собственные заголовки в реализациях порталов, использующих поточный вывод.
  • javax.portlet.servletDefaultSessionScope. Позволяет изменять видимость по умолчанию для объектов сессии, доступной сервлетам и JSP, вызываемым из портлета с использованием forward или include, с области видимости приложения на область видимости портлета. Данный метод совместного использования ресурсов позволяет работать с одинаковыми данными сессии портлету и сервлетным API, в частности, это важно для JSP, написанных с использованием сервлетных сред.
  • javax.portlet.actionScopedRequestAttributes. Разрешает Web-средам передавать составные объекты из фаз обработки действия или события в фазу отображения через объект запроса. Вы можете получать доступ к этим атрибутам до следующего запроса на действие (вызванного из обработчика действия, описанного в Action URL, или обработчика события). Эта возможность обычно реализуется контейнером портлетов с помощью сохранения атрибутов в объекте сессии и вызывает некоторое уменьшение производительности. Соответственно используйте ее только в том случае, если этого невозможно избежать. Данная опция - единственная, поддержка которой обязательна в JSR 286 для всех контейнеров.

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

Обратная совместимость

Java Portlet Specification версии 2.0 бинарно обратно совместима с портлетами спецификации 1.0 и даже сохраняет совместимость поведения всех методов API. Это значит, что все портлеты версии 1.0 могут быть запущены без всяких изменений в контейнере версии 2.0. Однако небольшие изменения поведения все-таки были введены. Эти изменения обычно не должны приводить к проблемам в работе любых портлетов версии 1.0:

  • RenderResponse.setContentType больше не требуется перед вызовом getWriter или getOutputStream. В соответствии со спецификацией 2.0 вызов getWriter или getOutputStream без предваряющей установки типа содержимого больше не вызывает IllegalStateException.
  • getProtocol для включаемых сервлетов/JSP больше не возвращает null, вместо этого он вернет HTTP/1.1.

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

В следующем разделе мы рассмотрим различные небольшие изменения, которые делают вашу жизнь легче и допускают новые способы использования, например, нововведения Java 5 в API, новые возможности кэширования, изменения ID времени исполнения, доступные вам, а также расширения библиотеки тегов.

Нововведения Java 5

JSR 286 поддерживает некоторые нововведения Java 5:

  • Использование генериков
  • Использование нового класса-перечисления для атрибутов профиля пользователя

GenericPortlet также позволяет вам использовать следующие аннотации, упрощающие управление вызовами методов жизненного цикла:

  • ProcessAction. Эта аннотация декларирует, что метод должен обрабатывать указанное действие. При этом URL действия должен включать параметр javax.portlet.action.
  • ProcessEvent. Эта аннотация декларирует метод, который должен обрабатывать указанное событие.
  • RenderMode. Эта аннотация декларирует метод, который должен обрабатывать вызов отрисовки указанного режима портлета.

В листинге 8 приведен пример использования аннотации ProcessAction.

Листинг 8. Использование аннотации ProcessAction
    // генерация URL
    PortletURL url = response.createActionURL();
    url.setParameter(ActionRequest.ACTION_NAME, "actionA");
    url.write(output);

    // метод для обработки действия
    @ProcessAction(name="actionA")
    void processMyActionA(ActionRequest req, ActionResponse resp)
    throws PortletException, java.io.IOException; {
    …
    }

Поскольку некоторые реализации порталов все еще используют Java 1.4, в JSR 286 были добавлены только те изменения, которые могут использоваться в порталах на Java 1.4. Для платформ, основанных на Java 1.4, файл JAR, использующий API JSR 286, компилируется в режиме совместимости с Java 1.4, при этом все несовместимые аспекты удаляются.

Новые возможности кэширования

JSR 286 добавляет новые возможности кэширования, помогая сделать производительность и масштабируемость портлетов выше:

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

В JSR 168 все содержимое кэша было доступно только для соответствующего пользователя. Если у вас есть портлеты, которые не применяют дополнительных настроек для каждого пользователя и всегда отображаются одинаково (например, портлет с новостями), контейнеру приходилось создавать для каждого пользователя новый элемент в кэше, пусть даже они были совершенно идентичны. В JSR 286 вы можете с помощью дескриптора развертывания указать контейнеру портлетов, что кэшированный элемент может быть общедоступен. Соответственно вы кардинально уменьшаете загрузку памяти для таких портлетов. Пример использования показан в листинге 9.

Листинг 9. Пример объявления публичной видимости кэша
<portlet>
    		...
      		<expiration-cache>60</expiration-cache>
		<cache-scope>public</cache-scope>
   		 ...
  	</portlet>

Область видимости кэша портлета также может быть установлена программно через метод объекта-ответа или через новый интерфейс CacheControl. Мы не рекомендуем программно изменять область видимости кэша, поскольку определение валидности содержимого кэша может быть весьма трудной задачей для контейнера, особенно если ваш портлет понижает область видимости кэша с публичной до частной для одного и того же пользователя.

Спецификация JSR 168 требовала от контейнера портлетов сброса содержимого кэша портлета для каждого пользователя после взаимодействия с портлетом (например, при вызове URL действия), так что на одного пользователя мог быть сохранен только один вариант кэша портлета. С добавлением публичных параметров отображения и разделяемого режима кэширования спецификация была усовершенствована, позволив контейнеру кэшировать несколько вариантов вида портлета, основываясь на параметрах отображения. Это усовершенствование означает, что вы можете использовать параметры отображения, а пользователи могут переключаться между несколькими видами, с сохранением различных вариантов отображения портала в общем кэше. Только тое действия или события, которые действительно вызывают эффект изменения содержимого, не отслеживаемый контейнером портлетов, теперь требуют сброса кэша.

Основанный на валидации механизм кэширования полезен для ситуаций, в которых вы не хотите часто перегенерировать содержимое портлета, поскольку это сложная и дорогая операция. В идеальном варианте надо установить время актуальности кэша в наивысшее из возможных значений, чтобы уже имеющееся содержимое было сохранено как можно дольше. С другой стороны, вы можете пожелать быстро отображать изменения в ваших системах хранения и обработки данных, изменяя содержимое портлета. Основанный на валидации кэш решает эту дилемму: он позволяет вам определить небольшое время актуальности кэша, если ваш портлет часто вызывается и проверяет изменения в системах хранения данных. Если никаких изменений в данных не произошло, вы сообщаете, что содержимое кэша все еще актуально, установив настройку CacheControl.setUseCachedContent(true) и выставляя новое время актуальности кэша. Если какие-то данные были изменены, то генерируете новое содержимое портлета и выставляете настройку в false.

Как вы могли бы узнать, что за содержимое системы хранения данных отображается в содержимом портлета? Например, вы можете создать собственное выражение проверки валидности, вызвав тег ETag при формировании ответа на HTTP-запрос. Контейнер портлетов предоставит вам информацию о валидности текущего содержимого портлета с помощью тега ETag при следующем отображении портлета или вызове serveResource.

В Листинге 10 содержится пример использования валидации кэшированных данных.

Листинг 10. Использование валидации кэшированных данных
protected void doView (RenderRequest request, RenderResponse response)
    throws PortletException, java.io.IOException
{
       if ( request.getETag() != null ) {  // проверка валидности
	if ( markupIsStillValid(request) { 
		// содержимое все еще актуально
   		response.getCacheControl().setExpirationTime(30);
		response.getCacheControl().setUseCachedContent(true);
		// нет необходимости формировать какой-либо вывод данных
		return;
	}
   }
   // создаем новое содержимое с тегом валидации
   response.getCacheControl().setETag(computeETag(request));
   response.getCacheControl().setExpirationTime(60);
   PortletRequestDispatcher rd = 
   getPortletContext().getPortletRequestDispatcher("jsp/view.jsp");
   rd.include(request, response);
}

private boolean markupIsStillValid(PortletRequest request) {
    // проверяем, соответствует ли состояние системы хранения данных текущему кэшу
    return computeETag(request).equals(request.getETag())
}

private String computeETag(PortletRequest request) {
    // возвращаем некоторый индикатор состояния системы хранения данных,
    // например - последнее время обновления
}

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

Идентификаторы времени исполнения

Идентификаторы времени исполнения могут использоваться для идентификации данных, обрабатываемых вашим портлетом, что позволяет уменьшить вероятность коллизий между несколькими экземплярами портлета в своем портале или даже на одной и той же странице. Большинство объектов API портлетов, например, сессии и настройки портлетов, имеют свои собственные пространства имен, но вам надо использовать внешнее пространство имен для совместного доступа к данным либо использовать уникальные идентификаторы для элементов вывода. Эта техника является общепринятой при использовании Ajax, что является одним из преимуществ спецификации портлетов версии 2.0.

Во-первых, JSR 286 продлила время жизни пространств имен идентификаторов времени исполнения, используя метод response.getNamespace, и теперь со времени жизни запроса оно увеличено до времени жизни окна портлета. Данное увеличение позволяет вам использовать пространство имен для создания пространства имен форм, если, например, ваш портлет предназначен для использования сервера порталов, основанного на формах. Также вы можете использовать пространство имен при вызове функций JavaScript, встроенных в ответ Ajax, или использовать пространство имен функций JavaScript, уже предоставленных в предыдущем ответе на запрос отрисовки портлета.

Во-вторых, JSR 286 предоставляет новый API вызова, позволяющий получать уникальный идентификатор для окна портлета, используя метод request.getWindowID. Данный метод возвращает идентификатор, который может быть использован как ключ данных, создаваемых в пространстве имен окна портлета (например, если портлет хочет кэшировать данные, полученные из системы хранения, в своем окне). Заметьте, однако, что вызовы жизненного цикла для портального окна не определены. Если вы хотите использовать идентификатор окна портлета для декларирования пространства имен данных, хранящихся в СУБД, вам придется очистить уже имеющиеся данные самостоятельно.

Расширения пользовательской библиотеки тегов

Библиотека тегов JSR 286 содержит свое собственное пространство имен, поэтому новые добавления никак не будут влиять на использование портлетов старого стандарта JSR 168. Вам нужно подключить новую библиотеку тегов с помощью оператора:

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

Определение объектов в тегах теперь усовершенствовано и предоставляет вам следующие дополнительные переменные для работы с запросом, ответом, и конфигурацией портлета, определенными в версии 1.0:

  • portletSession. Для доступа к сессии портлета.
  • portletSessionScope. Для доступа к атрибутам сессии портлета.
  • portletPreferences. Для доступа к настройкам портлета.
  • portletPreferencesValues. Для доступа к переменным настроек портлета в виде "ключ-значение".

Каждый из тегов генерации URL содержит следующие дополнительные атрибуты:

  • copyCurrentRenderParameters. Для копирования текущих параметров отображения в URL.
  • escapeXml. Для настройки экранирования XML в URL. Как уже упоминалось, в JSR 168 не было определено, будет ли XML в URL экранироваться или нет. JSR 286 определяет поведение, аналогичное Java Standard Tag Library: по умолчанию все URL экранируются, но вы можете отключить экранирование, используя атрибут escapeXml.

Для URL действий имеется также дополнительный атрибут, устанавливающий параметр javax.portlet.action, введенный в GenericPortlet для вызова аннотированных ProcessAction методов.

Другие добавления, сделанные в JSR 286, описаны ниже:

  • Добавлен новый тег resourceURL для формирования ресурсных URL
  • Добавлен тег propertyTag, который может использоваться при формировании портлетных URL для прикрепления текущих свойств URL

Заключение

Вторая версия Java Portlet Specification включает множество новых атрибутов и функций. Спецификация и ее API почти вдвое увеличились в сравнении с версией 1.0. Java Portlet Specification выросла и предлагает вам больше вариантов использования портлетов без необходимости применять нестандартные расширения от поставщиков контейнеров портлетов. Заметьте, что некоторые из описанных в статье новшеств являются необязательными, они могут не поддерживаться в некоторых JSR 286-совместимых серверах порталов, однако стандарт гарантирует, что если они все-таки реализованы, то это сделано в определенной и описанной в спецификации форме.

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=WebSphere, Технология Java
ArticleID=379894
ArticleTitle=Что нового в Java Portlet Specification V2.0 (JSR 286)?
publish-date=04032009