Доброго времени суток.
Я уже писал несколько статей о работе с новыми плагинами в Liferay 7.x. Настало время очередной такой статьи.
При миграции некоторого портала с Liferay 6.2 на 7.x потребовалось перенести существующий плагин для CKEditor'а: он менял версию редактора, кастомизировал панель инструментов и добавлял некоторых новых возможностей (интеграция с SyntaxHighlighter и что-то ещё). Я уже писал статью о создании JSP hooks в лайфрее - казалось бы, просто можно было бы следовать описанной инструкции. Но в итоге я наткнулся на несколько подводных камней, о которых и хотел бы рассказать.
Первое, с чем пришлось столкнуться - это исчезновение привычного CKEditor'а. При установке Liferay 7.x "с нуля" повсюду обнаруживается непривычный редактор.

Прошу любить и жаловать, гордость разработчиков лайфрея - AlloyEditor!



К сожалению, мы стараний команды лайфрея не оценили и захотели вернуться к привычному редактору. Как оказалось, это делается очень легко - достаточно лишь отредактировать файл portal-ext.properties (или создать его в $LIFERAY_HOME, если его ещё нет) и добавить в него следующие строки
editor.wysiwyg.default=ckeditor
editor.wysiwyg.portal-impl.portlet.ddm.text_html.ftl=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.announcements.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.blogs.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit_message.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.bb_code.jsp=ckeditor_bbcode
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.html.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.email_notification_settings.jsp=ckeditor
после чего перезапустить портал.
По именам свойств несложно понять, за что они отвечают. Возможно, вам будет важно знать, что ckeditor - не единственный доступный редактор, у вас есть выбор из следующих значений свойств:
	- alloyeditor
- alloyeditor_creole
- ckeditor
- ckeditor_bbcode
- ckeditor_creole
- simple
- tinymce
- tinymce_simple.
Итак, простыми манипуляциями мы вернули горячо любимый нами редактор. Что же дальше? А дальше нам хотелось бы немного подправить его под наши нужды: обновить версию, изменить порядок/номенклатуру кнопок в панели управления и так далее. Раньше это легко делалось с помощью хуков - можно было просто заменить существующие *.js файлы редактора прямо в портале. К сожалению, современные хуки (OSGI Fragments) не дают такой возможности - они позволяют заменить динамический контент (JSP-страницы) и добавить НОВЫЙ статический контент, но не дают возможности заменить старый. К счастью, мудрые разработчики лайфрея предусмотрели такую потребность и оставили "точку расширения" для конфигурации редактора. Как нынче водится, это очерденой OSGI-компонент.
Какова же будет последовательность действий? Для начала необходимо создать OSGI-плагин.
mvn archetype:generate -DarchetypeGroupId=com.liferay -DarchetypeArtifactId=com.liferay.project.templates.service -DliferayVersion=7.0 -DgroupId=test -DartifactId=ckeditot-config -Dversion=1.0 -Dpackage=test
И в проекте создать класс по образцу этого:
import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import org.osgi.service.component.annotations.Component;
import java.util.Map;
@Component(
        immediate = true,
        property = {
                "editor.name=ckeditor",
                "editor.name=alloyeditor",
                "editor.config.key=contentEditor",
                "javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet",
                "javax.portlet.name=com_liferay_blogs_web_portlet_BlogsAdminPortlet",
                "service.ranking:Integer=1000000"
        },
        service = EditorConfigContributor.class
)
public class EditorConfig extends BaseEditorConfigContributor {
    @Override
    public void populateConfigJSONObject(JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes, ThemeDisplay themeDisplay, RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
        //YOUR EDITOR CONFIGURATION HERE
    }
}
Давайте разберём по частям здесь написанное.
Для начала необходимо показать, что данный класс является сервисом, который будет подключен к "точке расширения". Для создания OSGI-сервиса используется аннотация @Component с указанием реализуемого сервиса (service = EditorConfigContributor.class), а для упрощения реализации используется наследование от класса BaseEditorConfigContributor.
А к какому из множества редакторов будет применена данная конфигурация? Их в портале великое множество, как по типам (AlloyEditor, CKEditor...), так и по экземплярам - один и тот же редактор используется в разных портлетах, а порой и по нескольку раз в рамках одного. Чтобы указать, к каким именно редакторам будет применена конфигурация, необходимо задать несколько свойств в массиве property аннотации @Component. 
Во-первых, можно указать тип редактора (ckeditor, alloyeditor и прочие, упомянутые ранее) посредством свойства editor.name ("editor.name=ckeditor").
Во-вторых, можно указать портлет, в котором будет изменена конфигурация редактора - свойство javax.portlet.name ("javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet").
В-третьих, можно указать конкретный редактор в портлете, так как их может быть несколько. Так, в блогах на странице редактирования есть несколько редакторов:
<div class="cover-image-caption <%= (coverImageFileEntryId == 0) ? "invisible" : "" %>">
    <small>
        <liferay-ui:input-editor
                contents="<%= coverImageCaption %>"
                editorName="alloyeditor"
                name="coverImageCaptionEditor"
                placeholder="caption"
                showSource="<%= false %>"
        />
    </small>
</div>
<div class="col-md-8 col-md-offset-2">
    <div class="entry-title form-group">
        <h1>
            <liferay-ui:input-editor
                    contents="<%= HtmlUtil.escape(title) %>"
                    editorName="alloyeditor"
                    name="titleEditor"
                    placeholder="title"
                    showSource="<%= false %>"
            />
        </h1>
    </div>
    <aui:input name="title" type="hidden" />
    <div class="entry-subtitle">
        <h4><liferay-ui:input-editor contents="<%= HtmlUtil.escape(subtitle) %>" editorName="alloyeditor" name="subtitleEditor" placeholder="subtitle" showSource="<%= false %>" /> </h4>
    </div>
Обратите внимание на тэг liferay-ui:input-editor - это и есть редактор. Каждый из них идентифицируется атрибутом name (name="subtitleEditor", name="titleEditor" и т.д.). И в массиве property можно указать нужное вам имя через editor.config.key ("editor.config.key=contentEditor").
Наконец, можно повысить приоритет данного класса по отношению к другим с помощью свойства service.ranking ("service.ranking:Integer=1000000").
После указания конфигурируемых редакторов можно приступить к самому процессу настройки. Вся работа происходит в методе populateConfigJSONObject, в который первым аргументом передаётся JSON-объект, в котором вы можете указать требуемые вам параметры настройки. Тут уже обращайтесь к документации по редактору, чтобы узнать, как совершить требуемые вам изменения.
В качестве примера можно рассмотреть изменение стандартной панели инструментов. В объекте конфигурации есть несколько свойств, отвечающих за внешний вид панели в различных ситуациях:
	- toolbar_liferayArticle
- toolbar_liferay
- toolbar_simple
- toolbar_editInPlace
- toolbar_tablet
- toolbar_email
А так будет выглядеть простейшая конфигурация для редактора в блогах:
JSONArray fst = JSONFactoryUtil.createJSONArray();
fst.put("Bold");
fst.put("Italic");
JSONArray snd = JSONFactoryUtil.createJSONArray();
snd.put("NumberedList");
snd.put("BulletedList");
JSONArray toolbars = JSONFactoryUtil.createJSONArray();
toolbars.put(fst);
toolbars.put("-");
toolbars.put(snd);
jsonObject.put("toolbar_liferay", toolbars);

Важно отметить, что в рамках портала существует множество "конфигураторов", и все они могут работать с одним и тем же JSON-объектом. Потому хорошей идеей будет повышение специфичности вашего сервиса - по возможности старайтесь указывать портлет, а порой даже и имя редактора. Я потратил немало времени, пытаясь изменить занчение свойства allowedContent на true (потребовалось для интеграции с SyntaxHighlighter) - в итоге проблема решилась, когда я указал в массиве свойств "editor.config.key=contentEditor".
На данном моменте позвольте откланяться. Краткое руководство по подключению плагинов к CKEditor я дам в следующих статьях этого уютного бложика.