DocumentService.java

/*
 * Copyright (c) 2002-2023, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.document.service;

import fr.paris.lutece.plugins.document.business.Document;
import fr.paris.lutece.plugins.document.business.DocumentHome;
import fr.paris.lutece.plugins.document.business.DocumentType;
import fr.paris.lutece.plugins.document.business.DocumentTypeHome;
import fr.paris.lutece.plugins.document.business.attributes.AttributeTypeParameter;
import fr.paris.lutece.plugins.document.business.attributes.DocumentAttribute;
import fr.paris.lutece.plugins.document.business.attributes.MapProviderManager;
import fr.paris.lutece.plugins.document.business.category.Category;
import fr.paris.lutece.plugins.document.business.category.CategoryHome;
import fr.paris.lutece.plugins.document.business.portlet.DocumentPortletHome;
import fr.paris.lutece.plugins.document.business.spaces.DocumentSpace;
import fr.paris.lutece.plugins.document.business.spaces.DocumentSpaceHome;
import fr.paris.lutece.plugins.document.business.workflow.DocumentAction;
import fr.paris.lutece.plugins.document.business.workflow.DocumentActionHome;
import fr.paris.lutece.plugins.document.service.metadata.MetadataHandler;
import fr.paris.lutece.plugins.document.service.publishing.PublishingService;
import fr.paris.lutece.plugins.document.service.spaces.DocumentSpacesService;
import fr.paris.lutece.plugins.document.utils.ImageUtils;
import fr.paris.lutece.plugins.document.utils.IntegerUtils;
import fr.paris.lutece.plugins.document.web.DocumentResourceServlet;
import fr.paris.lutece.portal.business.portlet.Portlet;
import fr.paris.lutece.portal.business.user.AdminUser;
import fr.paris.lutece.portal.service.cache.CacheService;
import fr.paris.lutece.portal.service.cache.CacheableService;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.message.AdminMessage;
import fr.paris.lutece.portal.service.message.AdminMessageService;
import fr.paris.lutece.portal.service.rbac.RBACResource;
import fr.paris.lutece.portal.service.rbac.RBACService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.web.constants.Messages;
import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
import fr.paris.lutece.util.date.DateUtil;
import fr.paris.lutece.util.string.StringUtil;
import fr.paris.lutece.util.xml.XmlUtil;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;

import java.sql.Timestamp;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;


/**
 * This Service manages document actions (create, move, delete, validate ...)
 * and notify listeners.
 */
public class DocumentService
{
    //PARAMETERS
    private static final String PARAMETER_DOCUMENT_TITLE = "document_title";
    private static final String PARAMETER_DOCUMENT_SUMMARY = "document_summary";
    private static final String PARAMETER_DOCUMENT_COMMENT = "document_comment";
    private static final String PARAMETER_VALIDITY_BEGIN = "document_validity_begin";
    private static final String PARAMETER_VALIDITY_END = "document_validity_end";
    private static final String PARAMETER_MAILING_LIST = "mailinglists";
    private static final String PARAMETER_PAGE_TEMPLATE_DOCUMENT_ID = "page_template_id";
    private static final String PARAMETER_SKIP_PORTLET = "document_skip_portlet";
    private static final String PARAMETER_SKIP_CATEGORIES = "document_skip_categories";
    private static final String PARAMETER_CATEGORY = "category_id";
    private static final String PARAMETER_ATTRIBUTE_UPDATE = "update_";
    private static final String PARAMETER_CROPPABLE = "_croppable";
    private static final String PARAMETER_WIDTH = "_width";

    //MESSAGES
    private static final String MESSAGE_ERROR_DATEEND_BEFORE_DATEBEGIN = "document.message.dateEndBeforeDateBegin";
    private static final String MESSAGE_INVALID_DATEEND = "document.message.invalidDateEnd";
    private static final String MESSAGE_INVALID_DATEBEGIN = "document.message.invalidDateBegin";
    private static final String MESSAGE_INVALID_DATE_BEFORE_70 = "document.message.invalidDate.before1970";
    private static final String MESSAGE_ATTRIBUTE_VALIDATION_ERROR = "document.message.attributeValidationError";
    private static final String MESSAGE_ATTRIBUTE_WIDTH_ERROR = "document.message.widthError";
    private static final String MESSAGE_ATTRIBUTE_RESIZE_ERROR = "document.message.resizeError";
    private static final String MESSAGE_EXTENSION_ERROR = "document.message.extensionError";
    private static DocumentService _singleton = new DocumentService(  );
    private static final String TAG_DOCUMENT_ID = "document-id";
    private static final String TAG_DOCUMENT_TITLE = "document-title";
    private static final String TAG_DOCUMENT_SUMMARY = "document-summary";
    private static final String TAG_DOCUMENT_DATE_BEGIN = "document-date-begin";
    private static final String TAG_DOCUMENT_DATE_END = "document-date-end";
    private static final String TAG_DOCUMENT_SKIP_PORTLET = "document-skip-portlet";
    private static final String TAG_DOCUMENT_SKIP_CATEGORIES = "document-skip-categories";
    private static final String TAG_DOCUMENT_CATEGORIES = "document-categories";
    private static final String TAG_DOCUMENT_CATEGORY = "category";
    private static final String TAG_CDATA_BEGIN = "<![CDATA[";
    private static final String TAG_CDATA_END = "]]>";
    private static DocumentEventListernersManager _managerEventListeners;

    /** Creates a new instance of DocumentService */
    private DocumentService(  )
    {
        _managerEventListeners = SpringContextService.getBean( "document.documentEventListernersManager" );
    }

    /**
     * Get the unique instance of the service
     *
     * @return The unique instance
     */
    public static DocumentService getInstance(  )
    {
        return _singleton;
    }

    /**
     * Build an XML document that contains document's data.
     *
     * @param document The document
     * @return An XML fragment containing document's data
     */
    String buildXmlContent( Document document )
    {
        StringBuffer sbXml = new StringBuffer(  );
        XmlUtil.beginElement( sbXml, document.getCodeDocumentType(  ) );

        XmlUtil.addElement( sbXml, TAG_DOCUMENT_ID, document.getId(  ) );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_TITLE, TAG_CDATA_BEGIN + document.getTitle(  ) + TAG_CDATA_END );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_SUMMARY, TAG_CDATA_BEGIN + document.getSummary(  ) + TAG_CDATA_END );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_DATE_BEGIN,
            DateUtil.getDateString( document.getDateValidityBegin(  ), I18nService.getDefaultLocale(  ) ) );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_DATE_END,
            DateUtil.getDateString( document.getDateValidityEnd(  ), I18nService.getDefaultLocale(  ) ) );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_SKIP_PORTLET, BooleanUtils.toStringTrueFalse( document.isSkipPortlet( ) ) );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_SKIP_CATEGORIES, BooleanUtils.toStringTrueFalse( document.isSkipCategories( ) ) );
        XmlUtil.addElement( sbXml, TAG_DOCUMENT_CATEGORIES, buildXmlCategories( document ) );

        DocumentType documentType = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType(  ) );

        for ( DocumentAttribute attribute : documentType.getAttributes(  ) )
        {
            DocumentAttribute attributeDocument = getDocumentAttribute( document, attribute.getId(  ) );

            if ( attributeDocument != null )
            {
                AttributeManager manager = AttributeService.getInstance(  )
                                                           .getManager( attributeDocument.getCodeAttributeType(  ) );
                XmlUtil.addElement( sbXml, document.getCodeDocumentType(  ) + "-" + attribute.getCode(  ),
                    manager.getAttributeXmlValue( document, attributeDocument ) );
            }
        }

        XmlUtil.endElement( sbXml, document.getCodeDocumentType(  ) );

        return sbXml.toString(  );
    }

    /**
     * Build XML content for the categories
     * @param document The document
     * @return An XML fragment containing document's categories
     */
    private String buildXmlCategories( Document document )
    {
        StringBuffer strListCategories = new StringBuffer(  );

        if ( ( document != null ) && ( document.getCategories(  ) != null ) && !document.getCategories(  ).isEmpty(  ) )
        {
            for ( Category category : document.getCategories(  ) )
            {
                XmlUtil.addElement( strListCategories, TAG_DOCUMENT_CATEGORY,
                    TAG_CDATA_BEGIN + category.getName(  ) + TAG_CDATA_END );
            }
        }

        return strListCategories.toString(  );
    }

    /**
     * Change the state of the document
     *
     * @param document The document
     * @param user The user doing the action
     * @param nStateId The new state Id
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void changeDocumentState( Document document, AdminUser user, int nStateId )
        throws DocumentException
    {
        document.setStateId( nStateId );
        DocumentHome.update( document, false );

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_STATE_CHANGED );
    }

    /**
     * Create a new document
     *
     * @param document The document
     * @param user The user doing the action
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void createDocument( Document document, AdminUser user )
        throws DocumentException
    {
        document.setId( DocumentHome.newPrimaryKey(  ) );
        document.setDateCreation( new Timestamp( new java.util.Date(  ).getTime(  ) ) );
        document.setDateModification( new Timestamp( new java.util.Date(  ).getTime(  ) ) );
        document.setXmlWorkingContent( buildXmlContent( document ) );

        DocumentHome.create( document );

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_CREATED );
    }

    /**
     * Modify a the content of a document
     *
     * @param document The document
     * @param user The user doing the action
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void modifyDocument( Document document, AdminUser user )
        throws DocumentException
    {
        document.setDateModification( new Timestamp( new java.util.Date(  ).getTime(  ) ) );
        document.setXmlWorkingContent( buildXmlContent( document ) );
        DocumentHome.update( document, true );

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_CONTENT_MODIFIED );
    }

    /**
     * Validate a document
     * @param nStateId The new state id for a validated document
     * @param document The document
     * @param user The user doing the action
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void validateDocument( Document document, AdminUser user, int nStateId )
        throws DocumentException
    {
        document.setStateId( nStateId );
        // Copy the working content into the validated content
        document.setXmlValidatedContent( document.getXmlWorkingContent(  ) );
        DocumentHome.update( document, false );
        DocumentHome.validateAttributes( document.getId(  ) );

        List<DocumentAttribute> listAttributes = document.getAttributes(  );

        for ( DocumentAttribute attribute : listAttributes )
        {
            DocumentResourceServlet.putInCache( document.getId(  ), attribute.getId(  ) );
        }

        for ( CacheableService cs : CacheService.getCacheableServicesList(  ) )
        {
            if ( cs.getClass(  ).equals( DocumentContentService.class ) )
            {
                for ( int nIdPortlet : DocumentPortletHome.findPortletForDocument( document.getId(  ) ) )
                {
                    ( (DocumentContentService) cs ).removeFromCache( Integer.toString( document.getId(  ) ),
                        Integer.toString( nIdPortlet ) );
                }
            }
        }

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_STATE_CHANGED );
    }

    /**
     * Archive a document
     * @param nStateId The new state id for a validated document
     * @param document The document
     * @param user The user doing the action
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void archiveDocument( Document document, AdminUser user, int nStateId )
        throws DocumentException
    {
        // Get the list of portlets linked with document 
        Collection<Portlet> listPortlet = PublishingService.getInstance(  )
                                                           .getPortletsByDocumentId( Integer.toString( 
                    document.getId(  ) ) );

        for ( Portlet portlet : listPortlet )
        {
            //If document is published, unpublish it
            if ( PublishingService.getInstance(  ).isPublished( document.getId(  ), portlet.getId(  ) ) )
            {
                PublishingService.getInstance(  ).unPublish( document.getId(  ), portlet.getId(  ) );
            }

            //Unassign Document to portlet
            PublishingService.getInstance(  ).unAssign( document.getId(  ), portlet.getId(  ) );
        }

        document.setStateId( nStateId );
        DocumentHome.update( document, false );

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_STATE_CHANGED );
    }

    /**
     * Move a document from a space to another
     *
     * @param document The document
     * @param user The user doing the action
     * @param nNewSpace The Id of the destination space
     * @throws DocumentException raise when error occurs in event or rule
     */
    public void moveDocument( Document document, AdminUser user, int nNewSpace )
        throws DocumentException
    {
        document.setSpaceId( nNewSpace );
        DocumentHome.update( document, false );

        notify( document.getId(  ), user, DocumentEvent.DOCUMENT_MOVED );
    }

    /**
     * Notify an event to all listeners
     *
     * @param nDocumentId The document Id
     * @param user The user doing the action
     * @param nEventType The type of event
     * @throws DocumentException raise when error occurs in event or rule
     */
    private void notify( int nDocumentId, AdminUser user, int nEventType )
        throws DocumentException
    {
        // Reload document to have all data (ie : state_key !)
        Document document = DocumentHome.findByPrimaryKeyWithoutBinaries( nDocumentId );
        DocumentEvent event = new DocumentEvent( document, user, nEventType );

        _managerEventListeners.notifyListeners( event );
    }

    /**
     * Add to the document all permitted actions according to the current user
     * and
     * using the current locale
     * @param user The current user
     * @param document The document
     * @param locale The Locale
     */
    public void getActions( Document document, Locale locale, AdminUser user )
    {
        List<DocumentAction> listActions = DocumentActionHome.getActionsList( document, locale );
        RBACResource documentType = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType(  ) );
        listActions = (List<DocumentAction>) RBACService.getAuthorizedActionsCollection( listActions, documentType, user );

        // Get all beans from the global ApplicationContext
        List<IDocumentActionsService> listActionServices = SpringContextService.getBeansOfType( IDocumentActionsService.class );

        // Process all services
        for ( IDocumentActionsService actionService : listActionServices )
        {
            listActions.addAll( actionService.getActions( document, locale, user ) );
        }

        document.setActions( listActions );
    }

    /**
     * Get the published status of a document
     * @param document The document to get the published status of
     */
    public void getPublishedStatus( Document document )
    {
        boolean isPublished = PublishingService.getInstance(  ).isPublished( document.getId(  ) );
        int nPublishedStatus = 0;

        if ( !isPublished )
        {
            nPublishedStatus = 1;
        }

        document.setPublishedStatus( nPublishedStatus );
    }

    /**
     * Build an HTML form for the document creation for a given document type
     *
     * @param strDocumentTypeCode The Document type code
     * @param locale The Locale
     * @param strBaseUrl The base Url
     *
     * @return The HTML form
     */
    public String getCreateForm( String strDocumentTypeCode, Locale locale, String strBaseUrl )
    {
        StringBuilder sbForm = new StringBuilder(  );
        DocumentType documentType = DocumentTypeHome.findByPrimaryKey( strDocumentTypeCode );

        for ( DocumentAttribute attribute : documentType.getAttributes(  ) )
        {
            AttributeManager manager = AttributeService.getInstance(  ).getManager( attribute.getCodeAttributeType(  ) );
            sbForm.append( manager.getCreateFormHtml( attribute, locale, strBaseUrl ) );
        }

        return sbForm.toString(  );
    }

    /**
     * Build an HTML form for the document modification for a given document ID.
     * <b>Warning</b> : This method loads the binaries of the document.
     *
     * @param strDocumentId The Id of the document to modify
     * @param locale The Locale
     * @param strBaseUrl The base url
     *
     * @return The HTML form
     */
    public String getModifyForm( String strDocumentId, Locale locale, String strBaseUrl )
    {
        int nDocumentId = IntegerUtils.convert( strDocumentId );
        Document document = DocumentHome.findByPrimaryKey( nDocumentId );

        if ( document != null )
        {
            return getModifyForm( document, locale, strBaseUrl );
        }

        return StringUtils.EMPTY;
    }

    /**
     * Build an HTML form for the document modification for a given document
     *
     * @param document The document
     * @param locale The Locale
     * @param strBaseUrl The base url
     * @return The HTML form
     */
    public String getModifyForm( Document document, Locale locale, String strBaseUrl )
    {
        StringBuilder sbForm = new StringBuilder(  );
        DocumentType documentType = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType(  ) );

        for ( DocumentAttribute attribute : documentType.getAttributes(  ) )
        {
            DocumentAttribute attributeDocument = getDocumentAttribute( document, attribute.getId(  ) );

            if ( attributeDocument != null )
            {
                AttributeManager manager = AttributeService.getInstance(  )
                                                           .getManager( attributeDocument.getCodeAttributeType(  ) );
                sbForm.append( manager.getModifyFormHtml( attributeDocument, document, locale, strBaseUrl ) );
            }
            else
            {
                AttributeManager manager = AttributeService.getInstance(  )
                                                           .getManager( attribute.getCodeAttributeType(  ) );
                sbForm.append( manager.getCreateFormHtml( attribute, locale, strBaseUrl ) );
            }
        }

        return sbForm.toString(  );
    }

    /**
     * Retrieve an attribute of document from its ID
     * @param document The document
     * @param nAttributeId The attribute ID
     * @return The attribute if found, otherwise null
     */
    private DocumentAttribute getDocumentAttribute( Document document, int nAttributeId )
    {
        for ( DocumentAttribute attribute : document.getAttributes(  ) )
        {
            if ( attribute.getId(  ) == nAttributeId )
            {
                return attribute;
            }
        }

        return null;
    }

    /**
     * Check that a given user is allowed to access a document type for a given
     * permission in a document space specified in parameter
     * If permission is document creation, check if document creation is allowed
     * for the specified space.
     * @param nIdSpace the id of the document space
     * @param strDocumentTypeId the id of the type document being considered
     * @param strPermission the permission needed
     * @param user the user trying to access the ressource
     * @return true if the user can access the given resource with the given
     *         permission, false otherwise
     */
    public boolean isAuthorizedAdminDocument( int nIdSpace, String strDocumentTypeId, String strPermission,
        AdminUser user )
    {
        DocumentSpace documentSpace = DocumentSpaceHome.findByPrimaryKey( nIdSpace );
        boolean bPermissionCreate = strPermission.equals( DocumentTypeResourceIdService.PERMISSION_CREATE )
            ? documentSpace.isDocumentCreationAllowed(  ) : true;

        return DocumentSpacesService.getInstance(  ).isAuthorizedViewByWorkgroup( documentSpace.getId(  ), user ) &&
        DocumentSpacesService.getInstance(  ).isAuthorizedViewByRole( documentSpace.getId(  ), user ) &&
        RBACService.isAuthorized( DocumentType.RESOURCE_TYPE, strDocumentTypeId, strPermission, user ) &&
        bPermissionCreate;
    }

    /**
     * Return the data of a document object
     * @param mRequest The MultipartHttpServletRequest
     * @param document The document object
     * @param locale The locale
     * @return data of document object
     */
    public String getDocumentData( MultipartHttpServletRequest mRequest, Document document, Locale locale )
    {
        String strDocumentTitle = mRequest.getParameter( PARAMETER_DOCUMENT_TITLE );
        String strDocumentSummary = mRequest.getParameter( PARAMETER_DOCUMENT_SUMMARY );
        String strDocumentComment = mRequest.getParameter( PARAMETER_DOCUMENT_COMMENT );
        String strDateValidityEnd = mRequest.getParameter( PARAMETER_VALIDITY_END );
        String strDateValidityBegin = mRequest.getParameter( PARAMETER_VALIDITY_BEGIN );
        String strMailingListId = mRequest.getParameter( PARAMETER_MAILING_LIST );
        int nMailingListId = IntegerUtils.convert( strMailingListId, 0 );
        String strPageTemplateDocumentId = mRequest.getParameter( PARAMETER_PAGE_TEMPLATE_DOCUMENT_ID );
        int nPageTemplateDocumentId = IntegerUtils.convert( strPageTemplateDocumentId, 0 );
        String strSkipPortlet = mRequest.getParameter( PARAMETER_SKIP_PORTLET );
        boolean bSkipPortlet = ( ( strSkipPortlet == null ) || "".equals( strSkipPortlet ) ) ? false : true;
        String strSkipCategories = mRequest.getParameter( PARAMETER_SKIP_CATEGORIES );
        boolean bSkipCategories = ( ( strSkipCategories == null ) || "".equals( strSkipCategories ) ) ? false : true;
        String[] arrayCategory = mRequest.getParameterValues( PARAMETER_CATEGORY );

        // Check for mandatory value
        if ( StringUtils.isBlank( strDocumentTitle ) || StringUtils.isBlank( strDocumentSummary ) )
        {
            return AdminMessageService.getMessageUrl( mRequest, Messages.MANDATORY_FIELDS, AdminMessage.TYPE_STOP );
        }

        // Check for illegal character character
        if ( StringUtil.containsHtmlSpecialCharacters( strDocumentTitle ) ||
                StringUtil.containsHtmlSpecialCharacters( strDocumentSummary ) )
        {
            return AdminMessageService.getMessageUrl( mRequest, Messages.MESSAGE_ILLEGAL_CHARACTER,
                AdminMessage.TYPE_STOP );
        }

        DocumentType documentType = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType(  ) );
        List<DocumentAttribute> listAttributes = documentType.getAttributes(  );

        for ( DocumentAttribute attribute : listAttributes )
        {
            String strAdminMessage = setAttribute( attribute, document, mRequest, locale );

            if ( strAdminMessage != null )
            {
                return strAdminMessage;
            }
        }
        
        Timestamp dateValidityBegin = null;
        Timestamp dateValidityEnd = null;

        if ( ( strDateValidityBegin != null ) && !strDateValidityBegin.equals( "" ) )
        {
            Date dateBegin = DateUtil.parseIsoDate( strDateValidityBegin );

            if ( ( dateBegin == null ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_INVALID_DATEBEGIN, AdminMessage.TYPE_STOP );
            }

            dateValidityBegin = new Timestamp( dateBegin.getTime(  ) );

            if ( dateValidityBegin.before( new Timestamp( 0 ) ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_INVALID_DATE_BEFORE_70,
                    AdminMessage.TYPE_STOP );
            }
        }

        if ( ( strDateValidityEnd != null ) && !strDateValidityEnd.equals( "" ) )
        {
            Date dateEnd = DateUtil.parseIsoDate( strDateValidityEnd );

            if ( ( dateEnd == null ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_INVALID_DATEEND, AdminMessage.TYPE_STOP );
            }

            dateValidityEnd = new Timestamp( dateEnd.getTime(  ) );

            if ( dateValidityEnd.before( new Timestamp( 0 ) ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_INVALID_DATE_BEFORE_70,
                    AdminMessage.TYPE_STOP );
            }
        }

        //validate period (dateEnd > dateBegin )
        if ( ( dateValidityBegin != null ) && ( dateValidityEnd != null ) )
        {
            if ( dateValidityEnd.before( dateValidityBegin ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_ERROR_DATEEND_BEFORE_DATEBEGIN,
                    AdminMessage.TYPE_STOP );
            }
        }

        document.setTitle( strDocumentTitle );
        document.setSummary( strDocumentSummary );
        document.setComment( strDocumentComment );
        document.setDateValidityBegin( dateValidityBegin );
        document.setDateValidityEnd( dateValidityEnd );
        document.setMailingListId( nMailingListId );
        document.setPageTemplateDocumentId( nPageTemplateDocumentId );
        document.setSkipPortlet( bSkipPortlet );
        document.setSkipCategories( bSkipCategories );

        MetadataHandler hMetadata = documentType.metadataHandler(  );

        if ( hMetadata != null )
        {
            document.setXmlMetadata( hMetadata.getXmlMetadata( mRequest.getParameterMap(  ) ) );
        }

        document.setAttributes( listAttributes );

        //Categories
        List<Category> listCategories = new ArrayList<Category>(  );

        if ( arrayCategory != null )
        {
            for ( String strIdCategory : arrayCategory )
            {
                listCategories.add( CategoryHome.find( IntegerUtils.convert( strIdCategory ) ) );
            }
        }

        document.setCategories( listCategories );

        return null; // No error
    }

    /**
     * Update the specify attribute with the mRequest parameters
     *
     * @param attribute The {@link DocumentAttribute} to update
     * @param document The {@link Document}
     * @param mRequest The multipart http request
     * @param locale The locale
     * @return an admin message if error or null else
     */
    private String setAttribute( DocumentAttribute attribute, Document document, MultipartHttpServletRequest mRequest,
        Locale locale )
    {
        String strParameterStringValue = mRequest.getParameter( attribute.getCode(  ) );
        FileItem fileParameterBinaryValue = mRequest.getFile( attribute.getCode(  ) );
        String strIsUpdatable = mRequest.getParameter( PARAMETER_ATTRIBUTE_UPDATE + attribute.getCode(  ) );
        String strToResize = mRequest.getParameter( attribute.getCode(  ) + PARAMETER_CROPPABLE );
        boolean bIsUpdatable = ( ( strIsUpdatable == null ) || strIsUpdatable.equals( "" ) ) ? false : true;
        boolean bToResize = ( ( strToResize == null ) || strToResize.equals( "" ) ) ? false : true;

        if ( strParameterStringValue != null ) // If the field is a string
        {
            // Check for mandatory value
            if ( attribute.isRequired(  ) && strParameterStringValue.trim(  ).equals( "" ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, Messages.MANDATORY_FIELDS, AdminMessage.TYPE_STOP );
            }

            // Check for specific attribute validation
            AttributeManager manager = AttributeService.getInstance(  ).getManager( attribute.getCodeAttributeType(  ) );
            String strValidationErrorMessage = manager.validateValue( attribute.getId(  ), strParameterStringValue,
                    locale );

            if ( strValidationErrorMessage != null )
            {
                String[] listArguments = { attribute.getName(  ), strValidationErrorMessage };

                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_ATTRIBUTE_VALIDATION_ERROR, listArguments,
                    AdminMessage.TYPE_STOP );
            }

            attribute.setTextValue( strParameterStringValue );
        }
        else if ( fileParameterBinaryValue != null ) // If the field is a file
        {
            attribute.setBinary( true );

            String strContentType = fileParameterBinaryValue.getContentType(  );
            byte[] bytes = fileParameterBinaryValue.get(  );
            String strFileName = fileParameterBinaryValue.getName(  );
            String strExtension = FilenameUtils.getExtension( strFileName );

            AttributeManager manager = AttributeService.getInstance(  ).getManager( attribute.getCodeAttributeType(  ) );

            if ( !bIsUpdatable )
            {
                // there is no new value then take the old file value
                DocumentAttribute oldAttribute = document.getAttribute( attribute.getCode(  ) );

                if ( ( oldAttribute != null ) && ( oldAttribute.getBinaryValue(  ) != null ) &&
                        ( oldAttribute.getBinaryValue(  ).length > 0 ) )
                {
                    bytes = oldAttribute.getBinaryValue(  );
                    strContentType = oldAttribute.getValueContentType(  );
                    strFileName = oldAttribute.getTextValue(  );
                    strExtension = FilenameUtils.getExtension( strFileName );
                }
            }

            List<AttributeTypeParameter> parameters = manager.getExtraParametersValues( locale, attribute.getId(  ) );

            String extensionList = StringUtils.EMPTY;

            if ( CollectionUtils.isNotEmpty( parameters ) &&
                    CollectionUtils.isNotEmpty( parameters.get( 0 ).getValueList(  ) ) )
            {
                extensionList = parameters.get( 0 ).getValueList(  ).get( 0 );
            }

            // Check for mandatory value
            if ( attribute.isRequired(  ) && ( ( bytes == null ) || ( bytes.length == 0 ) ) )
            {
                return AdminMessageService.getMessageUrl( mRequest, Messages.MANDATORY_FIELDS, AdminMessage.TYPE_STOP );
            }
            else if ( StringUtils.isNotBlank( extensionList ) && !extensionList.contains( strExtension ) )
            {
                Object[] params = new Object[2];
                params[0] = attribute.getName(  );
                params[1] = extensionList;

                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_EXTENSION_ERROR, params,
                    AdminMessage.TYPE_STOP );
            }

            // Check for specific attribute validation
            String strValidationErrorMessage = manager.validateValue( attribute.getId(  ), strFileName, locale );

            if ( strValidationErrorMessage != null )
            {
                String[] listArguments = { attribute.getName(  ), strValidationErrorMessage };

                return AdminMessageService.getMessageUrl( mRequest, MESSAGE_ATTRIBUTE_VALIDATION_ERROR, listArguments,
                    AdminMessage.TYPE_STOP );
            }

            if ( bToResize && !ArrayUtils.isEmpty( bytes ) )
            {
                // Resize image
                String strWidth = mRequest.getParameter( attribute.getCode(  ) + PARAMETER_WIDTH );

                if ( StringUtils.isBlank( strWidth ) || !StringUtils.isNumeric( strWidth ) )
                {
                    String[] listArguments = 
                        {
                            attribute.getName(  ),
                            I18nService.getLocalizedString( MESSAGE_ATTRIBUTE_WIDTH_ERROR, mRequest.getLocale(  ) )
                        };

                    return AdminMessageService.getMessageUrl( mRequest, MESSAGE_ATTRIBUTE_VALIDATION_ERROR,
                        listArguments, AdminMessage.TYPE_STOP );
                }

                try
                {
                    bytes = ImageUtils.resizeImage( bytes, Integer.valueOf( strWidth ) );
                }
                catch ( IOException e )
                {
                    return AdminMessageService.getMessageUrl( mRequest, MESSAGE_ATTRIBUTE_RESIZE_ERROR,
                        AdminMessage.TYPE_STOP );
                }
            }

            attribute.setBinaryValue( bytes );
            attribute.setValueContentType( strContentType );
            attribute.setTextValue( strFileName );
        }

        return null;
    }
}