Создание записи в даталисте и прикрепления файла к нему

Пишу первый раз в блог. Так что не пинайте сильно.
Пусть несколько сумбурно, но надеюсь что многим начинающим будет полезно.
Начал я с изучения статьи Ангелины http://www.ossportal.org/technologies/alfresco/blogs/572
и к статьи https://forums.alfresco.com/forum/developer-discussions/repository-servi...

<UPDATED>
Будет очень полезно посмотреть вот эту статью
http://confluence.ecm-alfresco.ru/display/workingexamples/Custom+DataList
</UPDATED>
Очень полезная статья. Но не хватает освещения еще двух очень важных вопросов:

1. как программно на яве добавить запись в даталист.
2. как программно на яве привязать файл из репозиторя к даталисту.

Итак, начнем. 
Откуда растут ноги?
Стоит задача получать документы из биллинга (счета и счета фактуры) Это файлы с описаниями. Создавать бизнес процессы по отправке и отслеживанию, что бы не дай бог, клиенты не вовремя заплатили...

Для этого был создан собственный тип реестра в Alvex.
Оставалась сущая безделица: создать программно запись в дата лист.
Основное, что нужно помнить, что все в альфреске - это Node из XML (как в линуксе все файл).
Задача получения документов сводится к нескольким маленьким:
1. нужно найти родительский нод (реестр) куда будем делать записи.
2. создать список необходимых аттрибутов у нового нода. (Ну в самом деле, зачем создавать пустой нод).
3. создать дочерний нод (запись) у родительского нода. Самое главное именно того типа, который требуется в используемом реестре, иначе не будут работать формы реестра. И отображение информации будет искажено.
4. Создать файл в репозитории альфреско.
      ( Почему именно там?
    1. Базовый тип alvexdt:object не содержит полей типа cm:content, значит содержимое файла некуда записать.
    2. Содержит ассоциацию alvexdt:files. Которая является ссылкой на файл.
         Есть рекомендация пользоваться ссылками, вместо использования содержимого, скорость работы сильно от этого зависит.)
5. Привязать созданный файл к новой записи. Т.е. банально заполнить ссылку.

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

Вообщем-то все достаточно просто. Главное внимание.

Описание модели:
<?xml version="1.0" encoding="UTF-8"?>
<model
 name="alvexoutdocskr:documents_model"
 xmlns="http://www.alfresco.org/model/dictionary/1.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.alfresco.org/model/dictionary/1.0 modelSchema.xsd">
 <imports>
  <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
  <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
  <import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
  <import uri="http://www.alfresco.org/model/datalist/1.0" prefix="dl"/>
  <import uri="http://alvexcore.com/prefix/alvexdt" prefix="alvexdt"/>
 </imports>
 <namespaces>
  <namespace uri="http://alvexcore.com/prefix/alvexoutdocskr" prefix="alvexoutdocskr"/>
 </namespaces>
 <constraints>
 </constraints>
 <types>
  <type name="alvexoutdocskr:document_outdocskr">
   <parent>alvexdt:object</parent>
   <properties>
    <property name="alvexoutdocskr:publishedDate">
     <type>d:date</type>
    </property>
    <property name="alvexoutdocskr:authorisedBy">
     <type>d:text</type>
    </property>
    <property name="alvexoutdocskr:sumContract">
     <type>d:float</type>
    </property>
   </properties>
   <associations>
    <association name="alvexoutdocskr:contractManagerkr">
     <title>Contract Manager</title>
     <source>
      <mandatory>false</mandatory>
      <many>true</many>
     </source>
     <target>
      <class>cm:person</class>
     </target>
    </association>
    <association name="alvexoutdocskr:contractManagerkrControl">
     <title>Control Contract Manager</title>
     <source>
      <mandatory>false</mandatory>
      <many>true</many>
     </source>
     <target>
      <class>cm:person</class>
     </target>
    </association>
   </associations>
   <mandatory-aspects>
    <aspect>alvexoutdocskr:withVendorInfokr</aspect>
   </mandatory-aspects>
  </type><----->
 </types>
 <aspects>
  <aspect name="alvexoutdocskr:withVendorInfokr">
   <properties>
    <property name="alvexoutdocskr:vendorCompanyNamekr">
     <type>d:text</type>
     <index enabled="true">
      <atomic>true</atomic>
      <stored>true</stored>
      <tokenised>both</tokenised>
     </index>
    </property>
   </properties>
  </aspect>
 </aspects>
</model>

описание форм:

<alfresco-config>
 <config evaluator="model-type" condition="alvexoutdocskr:document_outdocskr">
  <forms>
   <form id="datagrid">
    <field-visibility>
     <show id="alvexdt:id" />
     <show id="alvexdt:company" />
     <show id="alvexdt:contractor" />
     <show id="alvexoutdocskr:vendorCompanyNamekr" />
     <show id="alvexdt:signingDate" />
     <show id="alvexdt:expiryDate" />
     <show id="alvexoutdocskr:sumContract" />
    </field-visibility>
    <appearance>
     <field id="alvexdt:id" isSortKey="true" />
     <field id="alvexdt:relatedDocuments">
      <control template="Alvex.DatagridRecordRenderer"/>
     </field>
    </appearance>
   </form>
   <form>
    <field-visibility>
     <show id="alvexdt:id" />
     <show id="alvexdt:registerDate" />
     <show id="alvexdt:company" />
     <show id="alvexdt:agreementType" />
     <show id="alvexdt:agreementSummary" />
     <show id="alvexdt:contractor" />
     <show id="alvexdt:relatedDocuments" />
     <show id="alvexdt:documentManager" />
     <show id="alvexoutdocskr:vendorCompanyNamekr" />
     <show id="alvexoutdocskr:contractManagerkr" />
     <show id="alvexoutdocskr:contractManagerkrControl" />
     <show id="alvexdt:signingDate" />
     <show id="alvexdt:expiryDate" />
     <show id="alvexdt:signatory" />
     <show id="alvexdt:renew" />
     <show id="alvexdt:location" />
     <show id="alvexdt:files" />
     <show id="alvexoutdocskr:publishedDate"/>
     <show id="alvexoutdocskr:authorisedBy"/>
     <show id="alvexoutdocskr:sumContract"/>
    </field-visibility>
    <create-form template="/alvex-form.ftl" />
    <appearance>
     <set id="id" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="company" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="vendor" appearance="" label="" template="/org/alfresco/components/form/2-column-set.ftl"/>
     <set id="date" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="renew"/>
     <set id="summary" appearance="" label="" />
     <set id="files" appearance="" label=""/>
     <set id="related" appearance="" label="" />
     <set id="location" />
     <set id="manager" />
     <set id="sums" />
     <field set="id" id="alvexdt:id">
      <control template="/alvex-auto-numberer.ftl"/>
     </field>
     <field set="date" id="alvexdt:registerDate"/>
     <field set="id" id="alvexdt:agreementType">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexdt:company">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexdt:contractor">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexoutdocskr:vendorCompanyNamekr">
      <control template="/orgchart-picker.ftl" />
     </field>
     <field set="vendor" id="alvexoutdocskr:vendorCompanyNamekr" >
      <control template="/org/alfresco/components/form/controls/textfield.ftl">
       <control-param name="style">width: 98%</control-param>
      </control>
     </field>
     <field set="date" id="alvexdt:signingDate"/>
     <field set="date" id="alvexdt:expiryDate"/>
     <field set="date" id="alvexdt:signatory">
      <control template="/orgchart-picker.ftl" />
     </field>
     <field set="date" id="alvexoutdocskr:publishedDate"/>
     <field set="manager" id="alvexoutdocskr:authorisedBy"/>
     <field set="renew" id="alvexdt:renew"/>
     <field set="summary" id="alvexdt:agreementSummary">
      <control template="/alvex-mltext.ftl">
       <control-param name="style">width: 98%</control-param>
      </control>
     </field>
     <field set="files" id="alvexdt:files">
      <control template="/alvex-uploader.ftl">
       <control-param name="uploadDirectory">uploads</control-param>
       <control-param name="createUploadDirectory">true</control-param>
       <control-param name="viewType">mini</control-param>
      </control>
     </field>
     <field set="related" id="alvexdt:relatedDocuments">
      <control template="/alvex-docreg-picker.ftl"/>
     </field>
     <field set="location" id="alvexdt:location">
      <control template="/share-site-picker.ftl"/>
     </field>
     <field set="manager" id="alvexoutdocskr:contractManagerkr" />
     <field set="manager" id="alvexoutdocskr:contractManagerkrControl" />
     <field set="sums" id="alvexoutdocskr:sumContract" />
    </appearance>
   </form>
  </forms>
 </config>

 <config evaluator="node-type" condition="alvexoutdocskr:document_outdocskr">
  <forms>
   <form>
    <field-visibility>
     <show id="alvexdt:id" />
     <show id="alvexdt:registerDate" />
     <show id="alvexdt:company" />
     <show id="alvexdt:agreementType" />
     <show id="alvexdt:agreementSummary" />
     <show id="alvexdt:contractor" />
     <show id="alvexdt:relatedDocuments" />
     <show id="alvexdt:documentManager" />
     <show id="alvexoutdocskr:vendorCompanyNamekr" />
     <show id="alvexdt:signingDate" />
     <show id="alvexdt:expiryDate" />
     <show id="alvexdt:signatory" />
     <show id="alvexdt:renew" />
     <show id="alvexdt:location" />
     <show id="alvexdt:files" />
     <show id="alvexoutdocskr:contractManagerkr" />
     <show id="alvexoutdocskr:contractManagerkrControl" />
     <show id="alvexoutdocskr:publishedDate"/>
     <show id="alvexoutdocskr:authorisedBy"/>
     <show id="alvexoutdocskr:sumContract"/>
    </field-visibility>
    <view-form template="/alvex-form.ftl" />
    <edit-form template="/alvex-form.ftl" />
    <appearance>
     <set id="id" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="company" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="date" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/>
     <set id="renew"/>
     <set id="summary" appearance="" label="" />
     <set id="files" appearance="" label=""/>
     <set id="related" appearance="" label="" />
     <set id="location" />
     <set id="manager" />
     <set id="sums" />
     <field set="id" id="alvexdt:id">
      <control template="/alvex-auto-numberer.ftl"/>
     </field>
     <field set="id" id="alvexdt:registerDate"/>
     <field set="id" id="alvexdt:agreementType">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexdt:company">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexdt:contractor">
      <control template="/alvex-masterData-select.ftl"/>
     </field>
     <field set="company" id="alvexdt:documentManager">
      <control template="/orgchart-picker.ftl" />
     </field>
     <field set="vendor" id="alvexoutdocskr:vendorCompanyNamekr" />
     <field set="date" id="alvexdt:signingDate"/>
     <field set="date" id="alvexdt:expiryDate"/>
     <field set="date" id="alvexdt:signatory">
      <control template="/orgchart-picker.ftl" />
     </field>
     <field set="date" id="alvexoutdocskr:publishedDate"/>
     <field set="manager" id="alvexoutdocskr:authorisedBy"/>
     <field set="renew" id="alvexdt:renew"/>
     <field set="summary" id="alvexdt:agreementSummary">
      <control template="/alvex-mltext.ftl">
       <control-param name="style">width: 98%</control-param>
      </control>
     </field>
    <field set="files" id="alvexdt:files">
      <control template="/alvex-uploader.ftl">
       <control-param name="uploadDirectory">uploads</control-param>
       <control-param name="createUploadDirectory">true</control-param>
       <control-param name="viewType">mini</control-param>
      </control>
     </field>
     <field set="related" id="alvexdt:relatedDocuments">
      <control template="/alvex-docreg-picker.ftl"/>
     </field>
     <field set="location" id="alvexdt:location">
      <control template="/share-site-picker.ftl"/>
     </field>
     <field set="manager" id="alvexoutdocskr:contractManagerkr" />
     <field set="manager" id="alvexoutdocskr:contractManagerkrControl" />
     <field set="sums" id="alvexoutdocskr:sumContract" />
    </appearance>
   </form>
  </forms>
 </config>

</alfresco-config>


И сам текст вебскрипта:
package ru.kristall.webscripts;

import java.io.*;
import java.util.*;

import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.extensions.surf.RequestContext;
import org.json.JSONException;
import org.json.JSONObject;
import org.alfresco.repo.workflow.*;
import org.alfresco.service.cmr.workflow.*;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.repo.model.*;
import org.alfresco.service.namespace.*;
import org.alfresco.service.cmr.repository.*;
import org.alfresco.util.ISO8601DateFormat;
import org.alfresco.model.ContentModel;
import org.alfresco.model.DataListModel;
import org.alfresco.repo.jscript.ScriptLogger;
 
public class createRecord extends AbstractWebScript {
 
    ScriptLogger logger = new ScriptLogger();
 
    private ServiceRegistry registry;
    private Repository repository;
    private NodeService nodeService;
 
 
    private String WFHISTORY_FOLDER_NAME = "WFHISTORY";
    private String WFHISTORY_FILE_NAME= "wfhistory";
 
    /* Так как мы будем работать с историей workflow и репозиторием
    нам нужны текущие объекты ServiceRegistry и Repository
    */
    public void setServiceRegistry (ServiceRegistry registry) {
        this.registry = registry;
    }
 
    public void setRepository (Repository repository) {
        this.repository = repository;
    }
 
 
    /* Метод, находящий узел в альфреске
    */
    protected NodeRef getNodeRef(NodeRef parent, String path) {
        List<String> pathElements = new ArrayList<String>();
        StringTokenizer tokenizer = new StringTokenizer(path, "/");
        while (tokenizer.hasMoreTokens()) {
            String childName = tokenizer.nextToken();
            pathElements.add(childName);
        }
 
        NodeRef nodeRef = null;
        try {           
            nodeRef = this.registry.getFileFolderService().resolveNamePath(parent, pathElements).getNodeRef();
        }    catch(Exception fnfe)    {}       
        return nodeRef;
    }
 

/***
 * Получаем каталог, где создавать записи.
 * @return
 */
    public NodeRef getHome(String dir) {
        NodeRef pNode = null;
        NodeRef companyHomeRef = this.repository.getCompanyHome();
        logger.error("================================= CompanyHome="+companyHomeRef.toString());
        if (companyHomeRef!=null) {
            pNode = getNodeRef(companyHomeRef, dir);
            if (pNode!=null) {
                logger.error("================================= pNode="+pNode.toString());
            } else logger.error("Unable to locate "+dir+" path");
        }
        return pNode;
    }
   
    public void writeFile2Node(NodeRef target, NodeRef dir, String nameF, InputStream inp) throws IOException {
      //// запись файла в нод даталиста
     //  нод создаваемого файла
      NodeRef fileNode = null;

      // Объявляем, что этот нод это содержимое файла
      QName contentQName = QName.createQName("{http://www.alfresco.org/model/content/1.0}content");
      // создаем файл в выбранном каталоге альфреско ( помним что это нод)
      fileNode = this.registry.getFileFolderService().create(dir, nameF, contentQName).getNodeRef();
      //  Нод нужно наполнить содержимым
      ContentWriter writer = this.registry.getFileFolderService().getWriter(fileNode);
      writer.putContent(inp);
      //  А теперь привяжем созданный файл к новой записи в даталисте.
     // Обратите внимание на типизацию!!!! Создаем ассоциацию, и указываем базовый тип.
      nodeService.createAssociation(target, fileNode, QName.createQName(
              "http://alvexcore.com/prefix/alvexdt", "files"));
    }

  /*
   точка запуска вебскрипта
*/
 
    public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
        logger.error("Execute my webScript DirectiveHistory");
       // Ищем реестр, куда будем добавлять записи
        NodeRef rr =getHome("Сайты/test2/dataLists/Invoice");
        nodeService = registry.getNodeService();
        String dlName =(String) nodeService.getProperty(rr, ContentModel.PROP_TITLE);
        logger.error("dlName="+dlName);
        // Дата в Альфреске пишется вот таким образом.
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(Calendar.YEAR, 2014);
        cal.set(Calendar.MONTH, 10);
        cal.set(Calendar.DAY_OF_MONTH, 9);
        cal.set(Calendar.HOUR, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.set(Calendar.SECOND, 0);
        String isoStartDate = ISO8601DateFormat.format(cal.getTime());
 
        //
   /*
    Создаем запись в даталисте
*/
       
        /*
       Заполним данными запись, Запись заполняется как набор типизованных свойств.
       Обратите внимание как типизируются свойства и как задаются значения, Это ВАЖНО.
      Шаг вправо, шаг влево - попытка побега, прыжок на месте попытка улететь.
      Я заполнил только обязательные данные
*/
        Map<QName, Serializable> dataListProperties =new HashMap<QName, Serializable>();
        dataListProperties.put(ContentModel.PROP_NAME, "33");
        dataListProperties.put(QName.createQName("http://alvexcore.com/prefix/alvexdt","id"),"33");
        dataListProperties.put(QName.createQName("http://alvexcore.com/prefix/alvexdt","registerDate"),isoStartDate);
        dataListProperties.put(QName.createQName("http://alvexcore.com/prefix/alvexoutdocskr","publishedDate"),isoStartDate);
        // Создание нода дата листа отчета

      /*
         Вот так, одним методом создается запись в даталисте. И опять очень ВАЖНА типизация
         свойств. Имя это имя нода, т.е. вы сами называете как хотите. У меня значение берется как id
     */
        ChildAssociationRef dataList =nodeService.createNode(rr,
                   ContentModel.ASSOC_CONTAINS,
                   QName.createQName("http://www.alfresco.org/model/content/1.0","33"),
                   QName.createQName("http://alvexcore.com/prefix/alvexoutdocskr","document_outdocskr"),
                   dataListProperties);




        String name= "Test";       

      /*
       Мне было очень интересно как альфреска заполняет данные
      */
        List<ChildAssociationRef> children = nodeService.getChildAssocs(rr);
        for (ChildAssociationRef childAssoc : children) {
                NodeRef child = childAssoc.getChildRef();
                logger.error( "======!!!!====="+(String) nodeService.getProperty(child, ContentModel.PROP_NAME));
                logger.error( "======Class====="+child.getClass().getName());
                Map<QName, Serializable> properties = nodeService.getProperties(child);
                Iterator iterator = properties.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry mapEntry = (Map.Entry) iterator.next();
                    logger.error("The key is: " + mapEntry.getKey()    + ", value is :" + mapEntry.getValue());
                    try {
                     logger.error("sThe key is: " + mapEntry.getKey().toString()    + ", value is :" + mapEntry.getValue().toString());
                    }
                    catch (Exception ex) {
                         logger.error("null The key is: " + mapEntry.getKey()    + ", value is :" + mapEntry.getValue());

                    }
                }
               
               
        }
       
        /*
           Получил нод вновь созданной записи
          Она нам понадобиться когда будем привязывать файл к ней. А привязывать будем потому, что это кастомный тип, расширяющий базовый тип alvexdt:object, котрый сам не имеет поля типа cm:content а содержит только ассоциацию alvexdt:files. (Есть рекомндация, что использование ассоциация улучшает скорость работы, по сравнению со своими свойствами.)
       */

        NodeRef child = dataList.getChildRef();

        logger.error("=====Write file");
       // это содержимое файла
        String test = "TESIK \n\r Тестик \n\r";
        byte[] b = test.getBytes();
       // превращаем его в поток, ну мне так кажется что это универсальнее
        InputStream inp = new ByteArrayInputStream(b);
        logger.error("before getHome ");
        //  ищем каталог в альфреске, куда будем писать файл.
        NodeRef rr1 =getHome("WFHISTORY");
        logger.error("before writefile2node ");
        // создаем и привязываем файл.
        writeFile2Node(child,rr1, "Test.txt", inp);
        logger.error("after writefile2node ");
    }
}
1860