Модели контента в Alfresco

Здесь уже написано несколько постов об Alfresco с технической стороны. И о том, как работать с Workflow, и о том, как создавать свои java-классы, но почему-то упущены самые основы. Хочется исправить это упущение и описать структуру хранения и представления контента в Alfresco. Самые основы. Это и объясняет структуру некоторых xml в прошлых постах, и описывает, как решить некоторые задачи на Alfresco. Приступим.

Какая задача является основной для Alfresco? Хранение структурированных данных. Alfresco - это хранилище информации. А также средство работы с этой информацией. Но все это живет лишь "вокруг" самих данных.

 

Как же все эти данные в Alfresco хранятся?

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

То, какие метаданные есть у конкретного файла, зависит от модели контента. Также как расширение файла описывает тип его содержимого, модель контента описывает тип контента.

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

  • Фундаментальные типы данных и то, как данные этих типов должны храниться в базе данных. Например, без моделей контента, Alfresco не знала[1] бы разницу между типом String и Date. 
  • Высокоуровневые типы, такие как "content" и "folder" являются такими же моделями, как и модели собственной разработки "Отчет" или "Контракт".
  • Некоторые аспекты, работающие из коробки, такие как “auditable” и “classifiable”, являются частью модели, также как и “rate­able” или “comment­able”.
  • Свойства (они же метаданные) специфичны для каждого типа контента. 
  • Ограничения, применяемые к свойствам (например, по регулярному выражению, по длине или вообще выбор точного свойства из списка возможных.
  • Индексирование контента для поиска.
  • Взаимоотношения между разными типами контента.

Рассмотрим на примере, как создаются новые модели. Допустим, мы будем использовать Alfresco для хранения документов-заявок, поступающих сюда по итогам запросов пользователей. Они должны иметь номер, тип, важность и прочие свойства. Для того, чтобы эти свойства появились, требуется создать новую модель данных. Это делается созданием нового файла в папке tomcat/shared/classes/afresco/extension/. Назовем нашу модель "Запрос". В таком случае создадим файл request.xml. А теперь перейдем к его содержимому:

 

Объявление новой модели, импортирование существующих моделей и пространство имен: 

<?xml version="1.0" encoding="UTF-8"?> 
<!-- Определяем новую модель, описание модель должна соответствовать стандартному xmlns Alfresco -->
<model name="itd:request" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Добавляем немного сведений о модели. Эти данные не будут использоваться Alfresco, но пригодятся при дальнейшей доработке-->
    <description>ITD Demo model</description>
    <author>Oleg</author>
    <version>1.1</version>
<!-- Импортируем сведения о других моделях, элементы которых будут использоваться в данном описании. Если мы хотим, чтобы загружаемый в Alfresco контент строился на базе данной моделе, то обязательно нужно импортировать модели "d" и "cm". Если в рамках Вашей модели будут использоваться связи с другими типами контента (например, "Ответ на заявку"), добавьте их сюда -->
    <imports>
        <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"></import>
        <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"></import>
    </imports>  
<!-- Пространство имен и префикс нашей модели. Эти данные должны быть уникальны, то есть у Вас не может быть две модели с одинаковыми именами, областями или префиксами. Все компоненты модели (свойства, ассоциации, ограничения итд) должны начинаться с префикса, указанного здесь -->
    <namespaces>
        <namespace uri="http://www.itd-systems.ru/model/content/1.0" prefix="itd" />
    </namespaces>
</model>

 

Типы

Типы похожи на классы объектов в ООП. Примерами стандартных типов в Alfresco служат "Content", "Person" и "Folder". Создаваемые Вами собственные типы ограничены только вашей фантазией и требованиями бизнеса. Например, "Отчет о прибылях", "Медицинская запись", "Фильм", "Песня", "Комментарий".

В рамках "Запроса" создадим тип "Заявка". Этот тип должен обладать некоторыми свойствами и ассоциациями. Но об этом чуть позже. Сначала сам тип:

<types>
    <type name="itd:Request">
<!-- Имя типа в более читабельном виде, чем в прошлой строке -->
        <title>Заявка</title>
<!-- Родитель нашего типа. Он может быть и самостоятельным, но если мы хотим увидеть его в веб-интерфейсе и создать контент на базе нашей модели, то нужно сделать родителем "cm" -->
        <parent>cm:content</parent>         
        <properties>
        .....
        </properties> 
        
        <associations>
        .....
        </associations>
    </type>
</types>

 

Свойства

Свойство - это составная часть метаданных, ассоциированных с данным типом. Например, свойствами "Отчета об убытках", могут быть такие свойства как: "Имя работника", "Дата создания", "Проект", "Клиент" и т.д. Этот тип может так же иметь свойство "данные" для хранения файла с отчетом в формате excel или PDF. В нашем случае свойствами будут "Номер", "Категория", "Статус".

<property name="itd:Number">
    <title>Номер заявки</title>
<!-- Тип метаданных. Как правило используются два стандартных варианта: строки и числа. Эти типы прописаны в стандартном словаре Alfresco и подключаются в поле imports в начале (у нас уже сделано), потому в данном случае тип имеет префикс "d" -->
    <type>d:text</type>
<!-- Разрешить/запретить пользователю изменять значение этого поля. В данном случае пользователь может изменять номер заявки -->
    <protected>false</protected>
<!-- Является ли заполнение этого поля обязательным? -->
    <mandatory>true</mandatory>
<!-- Сколько таких полей может быть у одного документа? Номер заявки наверняка может быть только один -->
    <multiple>false</multiple>
<!-- Значение поля по умолчанию. Задавать не обязательно, но желательно, если само поле является обязательным -->
    <default>000</default>
</property>
 
<property name="itd:Category">
    <title>Категория</title>
    <type>d:text</type>
    <mandatory>false</mandatory>
<!-- Включение индексирования для полнотекстового поиска -->
    <index enabled="true">
<!-- Метод обновления индекса: in foreground, in background -->
        <atomic>true</atomic>
<!-- Хранить контент в индексе? -->
        <stored>false</stored>
<!-- Разбить данные на части или хранить единым блоком? -->
        <tokenised>true</tokenised>
    </index>
</property>
 
<property name="itd:Status">
    <title>Статус заявки</title>
    <type>d:text</type>
    <mandatory>true</mandatory>
<!-- Ограничения, накладываемые на данное поле. В данном случае мы используем ограничение "itd:status", о его устройстве дальше -->
    <constraints>
        <constraint ref="itd:status"/>
    </constraints>
</property>

 

Ассоциации

Ассоциации определяют отношения между типами. Без ассоциаций, модель может быть полна типами которые ссылаются на другие части данных. Возвращаясь к примеру с "Отчетом по прибыли" мы можем сохранить каждую строчку как отдельный объект. К типу "Отчет по прибыли" мы можем добавить тип "строчка отчета". Используя ассоциации мы можем сказать Alfresco об отношениях между этими типами. Ассоциации бывают двух типов: "равные ассоциации" и и ассоциации "подчинения". "Равные" ассоциации определяют отношения между двумя объектами, но не подчиняет их один другому. "Подчиненные" ассоциации напротив используются для того, чтобы показать такое подчинение. Приведенный выше пример является примером "подчиненных" ассоциаций. Другой пример - "Отчет" и "Документы приложения" - они будут связаны "равными" ассоциациями.

<associations>
    <association name="itd:relatedDocuments">
        <title>Дополнительные документы к заявке</title>
        <source>
<!-- К заявке могут не прилагаться дополнительные документы -->
            <mandatory>false</mandatory>
<!-- но если что-то прилагается, то этого может быть много -->
            <many>true</many>
        </source>
        <target>
<!-- Дополнительными документами могут являться любые файлы -->
            <class>cm:content</class>
            <mandatory>false</mandatory>
            <many>true</many>
        </target>
    </association>
</associations>

 

Ограничения

Ограничения используются для определения того, какие данные могут храниться в свойстве типа. Существуют четыре типа ограничений: REGEX, LIST, MINMAX, and LENGTH.

  • REGEX используется для ограничения свойства с помощью регулярных выражений
  • LIST используется для определения списка возможных значений
  • MINMAX для ограничения цифровых значений
  • LENGTH для определения длины свойства

Ограничения могут быть определены один раз и использоваться во всех моделях Alfresco. Например, везде можно использовать ограничение “cm:filename” для REGEX-ограничения по имени файла.

В рассмотренном выше свойстве "Статус" мы уже указали, что будем использовать некое ограничение на допустимое значение поля статуса заявки. Напишем теперь реализацию этого ограничения:

<constraints>
<!-- Для того, чтобы пользователи не придумывали множество ничего не говорящих статусов к заявкам, введем три стандартных статуса, а пользователи пусть выбирают из них -->
    <constraint name="itd:status" type="LIST">
        <parameter name="AllowedValues">
            <list>
                <value>Новая</value>
                <value>В процессе</value>
                <value>Закрыто</value>
            </list>
        </parameter>
    </constraint>
<!-- И еще одно органичение на будущее - сложность заявки. Теперь ограничение на число, потому просто выберем минимальное и максимальное значение -->
    <constraint name="itd:severity" type="MINMAX">
        <parameter name="minValue"><value>1</value></parameter>
        <parameter name="maxValue"><value>5</value></parameter>
    </constraint
</constraints>
 

 

Аспекты

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

Допустим, мы хотим подключить к своей модели данных аспект "Сложность" для заявки. Посмотрим, как это будет выглядеть:

<aspects>
    <aspect name="itd:Severity">
        <title>Сложность заявки</title>
        <properties>
            <property name="itd:severity">
                <type>d:text</type>
                <mandatory>true</mandatory>
                <constraints>
                    <constraint ref="itd:severity"/>
                </constraints>
            </property>
        </properties>
    </aspect>
</aspects>

Внутри аспектов мы не встречаем ничего нового. Просто представьте себе, что это просто внешний тип.

 

Подведем итог

Alfresco привередлива к последовательности блоков, рассмотренных выше в хаотичном порядке. Собирая их в единый xml-файл требуется соблюдать следующий порядок:

<model>
    <imports />
    <namespaces />
    <constraints />
    <types>
        <type>
            <properties />
            <associations />
        </type>         
    <type />
</types> <aspects />
</model>

Вот наша модель и готова. И теперь нам осталось самое интересное - увидеть нашу модель в действии! Но я дам вам немного времени на написание своей модели после прочтения этой статьи. У вас есть 2 дня на то, чтобы все попробовать и указать в комментах к статье на мои ошибки. А через 2 дня я опишу, что же надо сделать, чтобы увидеть результаты своих трудов в Alfresco Share и Alfresco Explorer.

Олег Китаков

okitakov [AT] gmail [DOT] com

483

Комментарии

Добрый день!

Спасибо за статью.

Когда ждать продолжение?

 Мм, кто-то все же ждет? Тогда завтра-послезавтра будет..

ждут,ждут

 конечно ждем!

 А продолжения так и нет? :)

 о, спасибо! :-)

А возможна ли композиция моделей? Нужно, к примеру, некоторый набор свойств сгрупировать в виде таблицы. И чтоб можно было добавлять строки в этой таблице.
Кажется, Вы не совсем поняли, что такое модели. Модели - это только набор свойств и связей объекта (например, документа). Как они у Вас отобразятся в интерфейсе зависит от конфигурации Share. В конфигурации Share можно группировать поля в set-ы, чтобы они отображались вместе. Можете описать подробнее, какую задачу решаете? Из вопроса не понятно. Если Вы имеете в виду реестры, то посмотрите в сторону http://www.alvexcore.com/ru/product/features/documents/#registers: свойства документов отображаются в виде таблицы, новые строки = новые документы. Для создания новых типов реестров также создаются модели.
Моя модель должна представлять собой "личный листок сотрудника". Среди прочих полей есть табличка "Выполняемая работа с начала трудовой деятельности", а сколько в ней записей ведь заранее неизвестно. Было бы идеально, если бы свойством типа мог выступать другой тип. Тогда выставил бы <multiple>true</multiple> и было бы клёво. Но так нельзя(как я понял) :(  

P.S. Спасибо за ответ. 
fufler аватар
А что по сути должно храниться в этой табличке?
А какая разница?
 Пару столбцов с текстовой инфой. 
fufler аватар
 Разница в том, что способ хранения данных может зависеть от того, что эти данные из себя представляют. Если это просто текст, используйте d:text, если это какие-то более сложные конструкции, то используйте свой тип и ассоциации.