com.liferay.dynamic.data.mapping.service.impl.DDMStructureLocalServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.dynamic.data.mapping.service.impl.DDMStructureLocalServiceImpl.java

Source

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.dynamic.data.mapping.service.impl;

import com.liferay.dynamic.data.mapping.background.task.DDMStructureIndexerBackgroundTaskExecutor;
import com.liferay.dynamic.data.mapping.exception.InvalidParentStructureException;
import com.liferay.dynamic.data.mapping.exception.InvalidStructureVersionException;
import com.liferay.dynamic.data.mapping.exception.NoSuchStructureException;
import com.liferay.dynamic.data.mapping.exception.RequiredStructureException;
import com.liferay.dynamic.data.mapping.exception.StructureDefinitionException;
import com.liferay.dynamic.data.mapping.exception.StructureDuplicateElementException;
import com.liferay.dynamic.data.mapping.exception.StructureDuplicateStructureKeyException;
import com.liferay.dynamic.data.mapping.exception.StructureNameException;
import com.liferay.dynamic.data.mapping.internal.util.DDMFormTemplateSynchonizer;
import com.liferay.dynamic.data.mapping.io.DDMFormJSONDeserializer;
import com.liferay.dynamic.data.mapping.io.DDMFormJSONSerializer;
import com.liferay.dynamic.data.mapping.io.DDMFormXSDDeserializer;
import com.liferay.dynamic.data.mapping.model.DDMDataProviderInstance;
import com.liferay.dynamic.data.mapping.model.DDMDataProviderInstanceLink;
import com.liferay.dynamic.data.mapping.model.DDMForm;
import com.liferay.dynamic.data.mapping.model.DDMFormField;
import com.liferay.dynamic.data.mapping.model.DDMFormLayout;
import com.liferay.dynamic.data.mapping.model.DDMFormRule;
import com.liferay.dynamic.data.mapping.model.DDMStructure;
import com.liferay.dynamic.data.mapping.model.DDMStructureConstants;
import com.liferay.dynamic.data.mapping.model.DDMStructureVersion;
import com.liferay.dynamic.data.mapping.model.DDMTemplate;
import com.liferay.dynamic.data.mapping.model.DDMTemplateConstants;
import com.liferay.dynamic.data.mapping.service.base.DDMStructureLocalServiceBaseImpl;
import com.liferay.dynamic.data.mapping.service.permission.DDMStructurePermission;
import com.liferay.dynamic.data.mapping.storage.StorageType;
import com.liferay.dynamic.data.mapping.util.DDM;
import com.liferay.dynamic.data.mapping.util.DDMXML;
import com.liferay.dynamic.data.mapping.validator.DDMFormValidationException;
import com.liferay.dynamic.data.mapping.validator.DDMFormValidator;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.backgroundtask.BackgroundTaskManager;
import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.exception.LocaleException;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.model.ResourceConstants;
import com.liferay.portal.kernel.model.SystemEventConstants;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.search.DDMStructureIndexer;
import com.liferay.portal.kernel.search.Indexer;
import com.liferay.portal.kernel.search.IndexerRegistryUtil;
import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
import com.liferay.portal.kernel.service.ServiceContext;
import com.liferay.portal.kernel.service.permission.ModelPermissions;
import com.liferay.portal.kernel.systemevent.SystemEvent;
import com.liferay.portal.kernel.transaction.TransactionCommitCallbackUtil;
import com.liferay.portal.kernel.util.Constants;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.GroupThreadLocal;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.MapUtil;
import com.liferay.portal.kernel.util.OrderByComparator;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.workflow.WorkflowConstants;
import com.liferay.portal.spring.extender.service.ServiceReference;

import java.io.Serializable;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Provides the local service for accessing, adding, deleting, and updating
 * dynamic data mapping (DDM) structures.
 *
 * <p>
 * DDM structures (structures) are used in Liferay to store structured content
 * like document types, dynamic data definitions, or web contents.
 * </p>
 *
 * <p>
 * Structures support inheritance via parent structures. They also support
 * multi-language names and descriptions.
 * </p>
 *
 * <p>
 * Structures can be related to many models in Liferay, such as those for web
 * contents, dynamic data lists, and documents. This relationship can be
 * established via the model's class name ID.
 * </p>
 *
 * @author Brian Wing Shun Chan
 * @author Bruno Basto
 * @author Marcellus Tavares
 * @author Juan Fernndez
 */
public class DDMStructureLocalServiceImpl extends DDMStructureLocalServiceBaseImpl {

    @Override
    public DDMStructure addStructure(long userId, long groupId, long parentStructureId, long classNameId,
            String structureKey, Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, DDMForm ddmForm,
            DDMFormLayout ddmFormLayout, String storageType, int type, ServiceContext serviceContext)
            throws PortalException {

        // Structure

        User user = userLocalService.getUser(userId);

        if (Validator.isNull(structureKey)) {
            structureKey = String.valueOf(counterLocalService.increment());
        } else {
            structureKey = StringUtil.toUpperCase(structureKey.trim());
        }

        validate(groupId, parentStructureId, classNameId, structureKey, nameMap, ddmForm);

        long structureId = counterLocalService.increment();

        DDMStructure structure = ddmStructurePersistence.create(structureId);

        structure.setUuid(serviceContext.getUuid());
        structure.setGroupId(groupId);
        structure.setCompanyId(user.getCompanyId());
        structure.setUserId(user.getUserId());
        structure.setUserName(user.getFullName());
        structure.setVersionUserId(user.getUserId());
        structure.setVersionUserName(user.getFullName());
        structure.setParentStructureId(parentStructureId);
        structure.setClassNameId(classNameId);
        structure.setStructureKey(structureKey);
        structure.setVersion(DDMStructureConstants.VERSION_DEFAULT);
        structure.setDescriptionMap(descriptionMap, ddmForm.getDefaultLocale());
        structure.setNameMap(nameMap, ddmForm.getDefaultLocale());
        structure.setDefinition(ddmFormJSONSerializer.serialize(ddmForm));
        structure.setStorageType(storageType);
        structure.setType(type);

        ddmStructurePersistence.update(structure);

        // Resources

        if (serviceContext.isAddGroupPermissions() || serviceContext.isAddGuestPermissions()) {

            addStructureResources(structure, serviceContext.isAddGroupPermissions(),
                    serviceContext.isAddGuestPermissions());
        } else {
            addStructureResources(structure, serviceContext.getModelPermissions());
        }

        // Structure version

        DDMStructureVersion structureVersion = addStructureVersion(user, structure,
                DDMStructureConstants.VERSION_DEFAULT, serviceContext);

        // Structure layout

        ddmStructureLayoutLocalService.addStructureLayout(userId, groupId, structureVersion.getStructureVersionId(),
                ddmFormLayout, serviceContext);

        // Data provider instance links

        addDataProviderInstanceLinks(groupId, structureId, ddmForm);

        return structure;
    }

    /**
     * Adds a structure referencing its parent structure.
     *
     * @param      userId the primary key of the structure's creator/owner
     * @param      groupId the primary key of the group
     * @param      parentStructureId the primary key of the parent structure
     *             (optionally {@link
     *             DDMStructureConstants#DEFAULT_PARENT_STRUCTURE_ID})
     * @param      classNameId the primary key of the class name for the
     *             structure's related model
     * @param      structureKey the unique string identifying the structure
     *             (optionally <code>null</code>)
     * @param      nameMap the structure's locales and localized names
     * @param      descriptionMap the structure's locales and localized
     *             descriptions
     * @param      definition the structure's XML schema definition
     * @param      storageType the structure's storage type. It can be "xml" or
     *             "expando". For more information, see {@link StorageType}.
     * @param      type the structure's type. For more information, see {@link
     *             DDMStructureConstants}.
     * @param      serviceContext the service context to be applied. Can set the
     *             UUID, creation date, modification date, guest permissions,
     *             and group permissions for the structure.
     * @return     the structure
     * @deprecated As of 2.1.0, replaced by {@link #addStructure(long, long,
     *             long, long, String, Map, Map, DDMForm, DDMFormLayout, String,
     *             int, ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure addStructure(long userId, long groupId, long parentStructureId, long classNameId,
            String structureKey, Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, String definition,
            String storageType, int type, ServiceContext serviceContext) throws PortalException {

        ddmXML.validateXML(definition);

        DDMForm ddmForm = ddmFormXSDDeserializer.deserialize(definition);

        DDMFormLayout ddmFormLayout = ddm.getDefaultDDMFormLayout(ddmForm);

        return addStructure(userId, groupId, parentStructureId, classNameId, structureKey, nameMap, descriptionMap,
                ddmForm, ddmFormLayout, storageType, type, serviceContext);
    }

    @Override
    public DDMStructure addStructure(long userId, long groupId, long classNameId, Map<Locale, String> nameMap,
            Map<Locale, String> descriptionMap, DDMForm ddmForm, DDMFormLayout ddmFormLayout, String storageType,
            ServiceContext serviceContext) throws PortalException {

        return addStructure(userId, groupId, DDMStructureConstants.DEFAULT_PARENT_STRUCTURE_ID, classNameId, null,
                nameMap, descriptionMap, ddmForm, ddmFormLayout, storageType, DDMStructureConstants.TYPE_DEFAULT,
                serviceContext);
    }

    /**
     * Adds a structure referencing a default parent structure, using the portal
     * property <code>dynamic.data.lists.storage.type</code> storage type and
     * default structure type.
     *
     * @param      userId the primary key of the structure's creator/owner
     * @param      groupId the primary key of the group
     * @param      classNameId the primary key of the class name for the
     *             structure's related model
     * @param      nameMap the structure's locales and localized names
     * @param      descriptionMap the structure's locales and localized
     *             descriptions
     * @param      definition the structure's XML schema definition
     * @param      serviceContext the service context to be applied. Can set the
     *             UUID, creation date, modification date, guest permissions,
     *             and group permissions for the structure.
     * @return     the structure
     * @deprecated As of 2.1.0, replaced by {@link #addStructure(long, long,
     *             long, Map, Map, DDMForm, DDMFormLayout, ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure addStructure(long userId, long groupId, long classNameId, Map<Locale, String> nameMap,
            Map<Locale, String> descriptionMap, String definition, ServiceContext serviceContext)
            throws PortalException {

        return addStructure(userId, groupId, DDMStructureConstants.DEFAULT_PARENT_STRUCTURE_ID, classNameId, null,
                nameMap, descriptionMap, definition, StorageType.JSON.toString(),
                DDMStructureConstants.TYPE_DEFAULT, serviceContext);
    }

    @Override
    public DDMStructure addStructure(long userId, long groupId, String parentStructureKey, long classNameId,
            String structureKey, Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, DDMForm ddmForm,
            DDMFormLayout ddmFormLayout, String storageType, int type, ServiceContext serviceContext)
            throws PortalException {

        DDMStructure parentStructure = fetchStructure(groupId, classNameId, parentStructureKey);

        long parentStructureId = DDMStructureConstants.DEFAULT_PARENT_STRUCTURE_ID;

        if (parentStructure != null) {
            parentStructureId = parentStructure.getStructureId();
        }

        return addStructure(userId, groupId, parentStructureId, classNameId, structureKey, nameMap, descriptionMap,
                ddmForm, ddmFormLayout, storageType, type, serviceContext);
    }

    /**
     * Adds a structure referencing a default parent structure if the parent
     * structure is not found.
     *
     * @param      userId the primary key of the structure's creator/owner
     * @param      groupId the primary key of the group
     * @param      parentStructureKey the unique string identifying the parent
     *             structure (optionally <code>null</code>)
     * @param      classNameId the primary key of the class name for the
     *             structure's related model
     * @param      structureKey the unique string identifying the structure
     *             (optionally <code>null</code>)
     * @param      nameMap the structure's locales and localized names
     * @param      descriptionMap the structure's locales and localized
     *             descriptions
     * @param      definition the structure's XML schema definition
     * @param      storageType the structure's storage type. It can be "xml" or
     *             "expando". For more information, see {@link StorageType}.
     * @param      type the structure's type. For more information, see {@link
     *             DDMStructureConstants}.
     * @param      serviceContext the service context to be applied. Can set the
     *             UUID, creation date, modification date, guest permissions and
     *             group permissions for the structure.
     * @return     the structure
     * @deprecated As of 2.1.0, replaced by {@link #addStructure(long, long,
     *             String, long, String, Map, Map, DDMForm, DDMFormLayout,
     *             String, int, ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure addStructure(long userId, long groupId, String parentStructureKey, long classNameId,
            String structureKey, Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, String definition,
            String storageType, int type, ServiceContext serviceContext) throws PortalException {

        ddmXML.validateXML(definition);

        DDMForm ddmForm = ddmFormXSDDeserializer.deserialize(definition);

        DDMFormLayout ddmFormLayout = ddm.getDefaultDDMFormLayout(ddmForm);

        return addStructure(userId, groupId, parentStructureKey, classNameId, structureKey, nameMap, descriptionMap,
                ddmForm, ddmFormLayout, storageType, type, serviceContext);
    }

    /**
     * Adds the resources to the structure.
     *
     * @param structure the structure to add resources to
     * @param addGroupPermissions whether to add group permissions
     * @param addGuestPermissions whether to add guest permissions
     */
    @Override
    public void addStructureResources(DDMStructure structure, boolean addGroupPermissions,
            boolean addGuestPermissions) throws PortalException {

        String resourceName = DDMStructurePermission.getStructureModelResourceName(structure.getClassName());

        resourceLocalService.addResources(structure.getCompanyId(), structure.getGroupId(), structure.getUserId(),
                resourceName, structure.getStructureId(), false, addGroupPermissions, addGuestPermissions);
    }

    /**
     * Adds the model resources with the permissions to the structure.
     *
     * @param structure the structure to add resources to
     * @param modelPermissions the model permissions to be added
     */
    @Override
    public void addStructureResources(DDMStructure structure, ModelPermissions modelPermissions)
            throws PortalException {

        String resourceName = DDMStructurePermission.getStructureModelResourceName(structure.getClassName());

        resourceLocalService.addModelResources(structure.getCompanyId(), structure.getGroupId(),
                structure.getUserId(), resourceName, structure.getStructureId(), modelPermissions);
    }

    /**
     * Copies a structure, creating a new structure with all the values
     * extracted from the original one. The new structure supports a new name
     * and description.
     *
     * @param  userId the primary key of the structure's creator/owner
     * @param  structureId the primary key of the structure to be copied
     * @param  nameMap the new structure's locales and localized names
     * @param  descriptionMap the new structure's locales and localized
     *         descriptions
     * @param  serviceContext the service context to be applied. Can set the
     *         UUID, creation date, modification date, guest permissions, and
     *         group permissions for the structure.
     * @return the new structure
     */
    @Override
    public DDMStructure copyStructure(long userId, long structureId, Map<Locale, String> nameMap,
            Map<Locale, String> descriptionMap, ServiceContext serviceContext) throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        return addStructure(userId, structure.getGroupId(), structure.getParentStructureId(),
                structure.getClassNameId(), null, nameMap, descriptionMap, structure.getDDMForm(),
                structure.getDDMFormLayout(), structure.getStorageType(), structure.getType(), serviceContext);
    }

    @Override
    public DDMStructure copyStructure(long userId, long structureId, ServiceContext serviceContext)
            throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        return addStructure(userId, structure.getGroupId(), structure.getParentStructureId(),
                structure.getClassNameId(), null, structure.getNameMap(), structure.getDescriptionMap(),
                structure.getDDMForm(), structure.getDDMFormLayout(), structure.getStorageType(),
                structure.getType(), serviceContext);
    }

    /**
     * Deletes the structure and its resources.
     *
     * <p>
     * Before deleting the structure, this method verifies whether the structure
     * is required by another entity. If it is needed, an exception is thrown.
     * </p>
     *
     * @param structure the structure to be deleted
     */
    @Override
    @SystemEvent(type = SystemEventConstants.TYPE_DELETE)
    public void deleteStructure(DDMStructure structure) throws PortalException {
        if (!GroupThreadLocal.isDeleteInProcess()) {
            if (ddmStructureLinkPersistence.countByStructureId(structure.getStructureId()) > 0) {

                throw new RequiredStructureException.MustNotDeleteStructureReferencedByStructureLinks(
                        structure.getStructureId());
            }

            if (ddmStructurePersistence.countByParentStructureId(structure.getStructureId()) > 0) {

                throw new RequiredStructureException.MustNotDeleteStructureThatHasChild(structure.getStructureId());
            }

            if (ddmTemplatePersistence.countByClassPK(structure.getStructureId()) > 0) {

                throw new RequiredStructureException.MustNotDeleteStructureReferencedByTemplates(
                        structure.getStructureId());
            }
        }

        // Structure

        ddmStructurePersistence.remove(structure);

        // Data provider instance links

        ddmDataProviderInstanceLinkPersistence.removeByStructureId(structure.getStructureId());

        // Structure links

        ddmStructureLinkPersistence.removeByStructureId(structure.getStructureId());

        // Structure versions

        List<DDMStructureVersion> structureVersions = ddmStructureVersionLocalService
                .getStructureVersions(structure.getStructureId());

        for (DDMStructureVersion structureVersion : structureVersions) {
            ddmStructureLayoutPersistence.removeByStructureVersionId(structureVersion.getStructureVersionId());

            ddmStructureVersionPersistence.remove(structureVersion);
        }

        // Resources

        String resourceName = DDMStructurePermission.getStructureModelResourceName(structure.getClassName());

        resourceLocalService.deleteResource(structure.getCompanyId(), resourceName,
                ResourceConstants.SCOPE_INDIVIDUAL, structure.getStructureId());

        // Background tasks

        String backgroundTaskName = DDMStructureIndexerBackgroundTaskExecutor
                .getBackgroundTaskName(structure.getStructureId());

        backgroundTaskmanager.deleteGroupBackgroundTasks(structure.getGroupId(), backgroundTaskName,
                DDMStructureIndexerBackgroundTaskExecutor.class.getName());
    }

    /**
     * Deletes the structure and its resources.
     *
     * <p>
     * Before deleting the structure, the system verifies whether the structure
     * is required by another entity. If it is needed, an exception is thrown.
     * </p>
     *
     * @param structureId the primary key of the structure to be deleted
     */
    @Override
    public void deleteStructure(long structureId) throws PortalException {
        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        ddmStructureLocalService.deleteStructure(structure);
    }

    /**
     * Deletes the matching structure and its resources.
     *
     * <p>
     * Before deleting the structure, the system verifies whether the structure
     * is required by another entity. If it is needed, an exception is thrown.
     * </p>
     *
     * @param groupId the primary key of the group
     * @param classNameId the primary key of the class name for the structure's
     *        related model
     * @param structureKey the unique string identifying the structure
     */
    @Override
    public void deleteStructure(long groupId, long classNameId, String structureKey) throws PortalException {

        structureKey = getStructureKey(structureKey);

        DDMStructure structure = ddmStructurePersistence.findByG_C_S(groupId, classNameId, structureKey);

        ddmStructureLocalService.deleteStructure(structure);
    }

    /**
     * Deletes all the structures of the group.
     *
     * <p>
     * Before deleting the structures, the system verifies whether each
     * structure is required by another entity. If any of the structures are
     * needed, an exception is thrown.
     * </p>
     *
     * @param groupId the primary key of the group
     */
    @Override
    public void deleteStructures(long groupId) throws PortalException {
        List<DDMStructure> structures = ddmStructurePersistence.findByGroupId(groupId);

        deleteStructures(structures);
    }

    @Override
    public void deleteStructures(long groupId, long classNameId) throws PortalException {

        List<DDMStructure> structures = ddmStructurePersistence.findByG_C(groupId, classNameId);

        deleteStructures(structures);
    }

    /**
     * Returns the structure with the ID.
     *
     * @param  structureId the primary key of the structure
     * @return the structure with the structure ID, or <code>null</code> if a
     *         matching structure could not be found
     */
    @Override
    public DDMStructure fetchStructure(long structureId) {
        return ddmStructurePersistence.fetchByPrimaryKey(structureId);
    }

    /**
     * Returns the structure matching the class name ID, structure key, and
     * group.
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  structureKey the unique string identifying the structure
     * @return the matching structure, or <code>null</code> if a matching
     *         structure could not be found
     */
    @Override
    public DDMStructure fetchStructure(long groupId, long classNameId, String structureKey) {

        structureKey = getStructureKey(structureKey);

        return ddmStructurePersistence.fetchByG_C_S(groupId, classNameId, structureKey);
    }

    /**
     * Returns the structure matching the class name ID, structure key, and
     * group, optionally searching ancestor sites (that have sharing enabled)
     * and global scoped sites.
     *
     * <p>
     * This method first searches in the group. If the structure is still not
     * found and <code>includeAncestorStructures</code> is set to
     * <code>true</code>, this method searches the group's ancestor sites (that
     * have sharing enabled) and lastly searches global scoped sites.
     * </p>
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  structureKey the unique string identifying the structure
     * @param  includeAncestorStructures whether to include ancestor sites (that
     *         have sharing enabled) and include global scoped sites in the
     *         search
     * @return the matching structure, or <code>null</code> if a matching
     *         structure could not be found
     */
    @Override
    public DDMStructure fetchStructure(long groupId, long classNameId, String structureKey,
            boolean includeAncestorStructures) {

        structureKey = getStructureKey(structureKey);

        DDMStructure structure = ddmStructurePersistence.fetchByG_C_S(groupId, classNameId, structureKey);

        if (structure != null) {
            return structure;
        }

        if (!includeAncestorStructures) {
            return null;
        }

        for (long ancestorSiteGroupId : PortalUtil.getAncestorSiteGroupIds(groupId)) {

            structure = ddmStructurePersistence.fetchByG_C_S(ancestorSiteGroupId, classNameId, structureKey);

            if (structure != null) {
                return structure;
            }
        }

        return null;
    }

    @Override
    public List<DDMStructure> getChildrenStructures(long parentStructureId) {
        return ddmStructurePersistence.findByParentStructureId(parentStructureId);
    }

    /**
     * Returns all the structures matching the class name ID.
     *
     * @param  companyId the primary key of the structure's company
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @return the structures matching the class name ID
     */
    @Override
    public List<DDMStructure> getClassStructures(long companyId, long classNameId) {

        return ddmStructurePersistence.findByC_C(companyId, classNameId);
    }

    /**
     * Returns a range of all the structures matching the class name ID.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  companyId the primary key of the structure's company
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @return the range of matching structures
     */
    @Override
    public List<DDMStructure> getClassStructures(long companyId, long classNameId, int start, int end) {

        return ddmStructurePersistence.findByC_C(companyId, classNameId, start, end);
    }

    /**
     * Returns all the structures matching the class name ID ordered by the
     * comparator.
     *
     * @param  companyId the primary key of the structure's company
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  orderByComparator the comparator to order the structures
     *         (optionally <code>null</code>)
     * @return the matching structures ordered by the comparator
     */
    @Override
    public List<DDMStructure> getClassStructures(long companyId, long classNameId,
            OrderByComparator<DDMStructure> orderByComparator) {

        return ddmStructurePersistence.findByC_C(companyId, classNameId, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
                orderByComparator);
    }

    /**
     * Returns the structure with the ID.
     *
     * @param  structureId the primary key of the structure
     * @return the structure with the ID
     */
    @Override
    public DDMStructure getStructure(long structureId) throws PortalException {
        return ddmStructurePersistence.findByPrimaryKey(structureId);
    }

    /**
     * Returns the structure matching the class name ID, structure key, and
     * group.
     *
     * @param  groupId the primary key of the structure's group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  structureKey the unique string identifying the structure
     * @return the matching structure
     */
    @Override
    public DDMStructure getStructure(long groupId, long classNameId, String structureKey) throws PortalException {

        structureKey = getStructureKey(structureKey);

        return ddmStructurePersistence.findByG_C_S(groupId, classNameId, structureKey);
    }

    /**
     * Returns the structure matching the class name ID, structure key, and
     * group, optionally searching ancestor sites (that have sharing enabled)
     * and global scoped sites.
     *
     * <p>
     * This method first searches in the group. If the structure is still not
     * found and <code>includeAncestorStructures</code> is set to
     * <code>true</code>, this method searches the group's ancestor sites (that
     * have sharing enabled) and lastly searches global scoped sites.
     * </p>
     *
     * @param  groupId the primary key of the structure's group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  structureKey the unique string identifying the structure
     * @param  includeAncestorStructures whether to include ancestor sites (that
     *         have sharing enabled) and include global scoped sites in the
     *         search in the search
     * @return the matching structure
     */
    @Override
    public DDMStructure getStructure(long groupId, long classNameId, String structureKey,
            boolean includeAncestorStructures) throws PortalException {

        structureKey = getStructureKey(structureKey);

        DDMStructure structure = ddmStructurePersistence.fetchByG_C_S(groupId, classNameId, structureKey);

        if (structure != null) {
            return structure;
        }

        if (!includeAncestorStructures) {
            throw new NoSuchStructureException("No DDMStructure exists with the structure key " + structureKey);
        }

        for (long curGroupId : PortalUtil.getAncestorSiteGroupIds(groupId)) {
            structure = ddmStructurePersistence.fetchByG_C_S(curGroupId, classNameId, structureKey);

            if (structure != null) {
                return structure;
            }
        }

        throw new NoSuchStructureException(
                "No DDMStructure exists with the structure key " + structureKey + " in the ancestor groups");
    }

    /**
     * Returns all the structures matching the group, name, and description.
     *
     * @param  groupId the primary key of the structure's group
     * @param  name the structure's name
     * @param  description the structure's description
     * @return the matching structures
     */
    @Override
    public List<DDMStructure> getStructure(long groupId, String name, String description) {

        return ddmStructurePersistence.findByG_N_D(groupId, name, description);
    }

    @Override
    public DDMForm getStructureDDMForm(DDMStructure structure) throws PortalException {

        return ddmFormJSONDeserializer.deserialize(structure.getDefinition());
    }

    /**
     * Returns all the structures present in the system.
     *
     * @return the structures present in the system
     */
    @Override
    public List<DDMStructure> getStructures() {
        return ddmStructurePersistence.findAll();
    }

    /**
     * Returns all the structures present in the group.
     *
     * @param  groupId the primary key of the group
     * @return the structures present in the group
     */
    @Override
    public List<DDMStructure> getStructures(long groupId) {
        return ddmStructurePersistence.findByGroupId(groupId);
    }

    /**
     * Returns a range of all the structures belonging to the group.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  groupId the primary key of the group
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @return the range of matching structures
     */
    @Override
    public List<DDMStructure> getStructures(long groupId, int start, int end) {
        return ddmStructurePersistence.findByGroupId(groupId, start, end);
    }

    /**
     * Returns all the structures matching class name ID and group.
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @return the matching structures
     */
    @Override
    public List<DDMStructure> getStructures(long groupId, long classNameId) {
        return ddmStructurePersistence.findByG_C(groupId, classNameId);
    }

    /**
     * Returns a range of all the structures that match the class name ID and
     * group.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @return the range of matching structures
     */
    @Override
    public List<DDMStructure> getStructures(long groupId, long classNameId, int start, int end) {

        return ddmStructurePersistence.findByG_C(groupId, classNameId, start, end);
    }

    /**
     * Returns an ordered range of all the structures matching the class name ID
     * and group.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @param  orderByComparator the comparator to order the structures
     *         (optionally <code>null</code>)
     * @return the range of matching structures ordered by the comparator
     */
    @Override
    public List<DDMStructure> getStructures(long groupId, long classNameId, int start, int end,
            OrderByComparator<DDMStructure> orderByComparator) {

        return ddmStructurePersistence.findByG_C(groupId, classNameId, start, end, orderByComparator);
    }

    @Override
    public List<DDMStructure> getStructures(long groupId, String name, String description) {

        return ddmStructurePersistence.findByG_N_D(groupId, name, description);
    }

    /**
     * Returns all the structures belonging to the groups.
     *
     * @param  groupIds the primary keys of the groups
     * @return the structures belonging to the groups
     */
    @Override
    public List<DDMStructure> getStructures(long[] groupIds) {
        return ddmStructurePersistence.findByGroupId(groupIds);
    }

    /**
     * Returns all the structures matching the class name ID and belonging to
     * the groups.
     *
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @return the matching structures
     */
    @Override
    public List<DDMStructure> getStructures(long[] groupIds, long classNameId) {
        return ddmStructurePersistence.findByG_C(groupIds, classNameId);
    }

    /**
     * Returns a range of all the structures matching the class name ID and
     * belonging to the groups.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @return the range of matching structures
     */
    @Override
    public List<DDMStructure> getStructures(long[] groupIds, long classNameId, int start, int end) {

        return ddmStructurePersistence.findByG_C(groupIds, classNameId, start, end);
    }

    /**
     * Returns an ordered range of all the structures matching the group, class
     * name ID, name, and description.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @param  name the name keywords
     * @param  description the description keywords
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @param  orderByComparator the comparator to order the structures
     *         (optionally <code>null</code>)
     * @return the range of matching structures ordered by the comparator
     */
    @Override
    public List<DDMStructure> getStructures(long[] groupIds, long classNameId, String name, String description,
            int start, int end, OrderByComparator<DDMStructure> orderByComparator) {

        return ddmStructurePersistence.findByG_C_N_D(groupIds, classNameId, name, description, start, end,
                orderByComparator);
    }

    /**
     * Returns the number of structures belonging to the group.
     *
     * @param  groupId the primary key of the group
     * @return the number of structures belonging to the group
     */
    @Override
    public int getStructuresCount(long groupId) {
        return ddmStructurePersistence.countByGroupId(groupId);
    }

    /**
     * Returns the number of structures matching the class name ID and group.
     *
     * @param  groupId the primary key of the group
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @return the number of matching structures
     */
    @Override
    public int getStructuresCount(long groupId, long classNameId) {
        return ddmStructurePersistence.countByG_C(groupId, classNameId);
    }

    /**
     * Returns the number of structures matching the class name ID and belonging
     * to the groups.
     *
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name for the structure's
     *         related model
     * @return the number of matching structures
     */
    @Override
    public int getStructuresCount(long[] groupIds, long classNameId) {
        return ddmStructurePersistence.countByG_C(groupIds, classNameId);
    }

    @Override
    public String prepareLocalizedDefinitionForImport(DDMStructure structure, Locale defaultImportLocale) {

        DDMForm ddmForm = ddm.updateDDMFormDefaultLocale(structure.getDDMForm(), defaultImportLocale);

        return ddmFormJSONSerializer.serialize(ddmForm);
    }

    @Override
    public void revertStructure(long userId, long structureId, String version, ServiceContext serviceContext)
            throws PortalException {

        DDMStructureVersion structureVersion = ddmStructureVersionLocalService.getStructureVersion(structureId,
                version);

        if (!structureVersion.isApproved()) {
            throw new InvalidStructureVersionException("Unable to revert from an unapproved file version");
        }

        DDMStructure structure = structureVersion.getStructure();

        serviceContext.setAttribute("majorVersion", Boolean.TRUE);
        serviceContext.setAttribute("status", WorkflowConstants.STATUS_APPROVED);
        serviceContext.setCommand(Constants.REVERT);

        ddmStructureLocalService.updateStructure(userId, structure.getGroupId(),
                structureVersion.getParentStructureId(), structure.getClassNameId(), structure.getStructureKey(),
                structureVersion.getNameMap(), structureVersion.getDescriptionMap(), structureVersion.getDDMForm(),
                structureVersion.getDDMFormLayout(), serviceContext);
    }

    @Override
    public List<DDMStructure> search(long companyId, long[] groupIds, long classNameId, long classPK,
            String keywords, int start, int end, OrderByComparator<DDMStructure> orderByComparator)
            throws PortalException {

        return ddmStructureFinder.findByKeywords(companyId, groupIds, classNameId, classPK, keywords, start, end,
                orderByComparator);
    }

    /**
     * Returns an ordered range of all the structures matching the groups and
     * class name IDs, and matching the keywords in the structure names and
     * descriptions.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  companyId the primary key of the structure's company
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name of the model the
     *         structure is related to
     * @param  keywords the keywords (space separated), which may occur in the
     *         structure's name or description (optionally <code>null</code>)
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @param  orderByComparator the comparator to order the structures
     *         (optionally <code>null</code>)
     * @return the range of matching structures ordered by the comparator
     */
    @Override
    public List<DDMStructure> search(long companyId, long[] groupIds, long classNameId, String keywords, int status,
            int start, int end, OrderByComparator<DDMStructure> orderByComparator) {

        return ddmStructureFinder.findByKeywords(companyId, groupIds, classNameId, keywords, status, start, end,
                orderByComparator);
    }

    /**
     * Returns an ordered range of all the structures matching the groups, class
     * name IDs, name keyword, description keyword, storage type, and type.
     *
     * <p>
     * Useful when paginating results. Returns a maximum of <code>end -
     * start</code> instances. <code>start</code> and <code>end</code> are not
     * primary keys, they are indexes in the result set. Thus, <code>0</code>
     * refers to the first result in the set. Setting both <code>start</code>
     * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
     * result set.
     * </p>
     *
     * @param  companyId the primary key of the structure's company
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name of the model the
     *         structure is related to
     * @param  name the name keywords
     * @param  description the description keywords
     * @param  storageType the structure's storage type. It can be "xml" or
     *         "expando". For more information, see {@link StorageType}.
     * @param  type the structure's type. For more information, see {@link
     *         DDMStructureConstants}.
     * @param  andOperator whether every field must match its keywords, or just
     *         one field
     * @param  start the lower bound of the range of structures to return
     * @param  end the upper bound of the range of structures to return (not
     *         inclusive)
     * @param  orderByComparator the comparator to order the structures
     *         (optionally <code>null</code>)
     * @return the range of matching structures ordered by the comparator
     */
    @Override
    public List<DDMStructure> search(long companyId, long[] groupIds, long classNameId, String name,
            String description, String storageType, int type, int status, boolean andOperator, int start, int end,
            OrderByComparator<DDMStructure> orderByComparator) {

        return ddmStructureFinder.findByC_G_C_N_D_S_T_S(companyId, groupIds, classNameId, name, description,
                storageType, type, status, andOperator, start, end, orderByComparator);
    }

    @Override
    public int searchCount(long companyId, long[] groupIds, long classNameId, long classPK, String keywords)
            throws PortalException {

        return ddmStructureFinder.countByKeywords(companyId, groupIds, classNameId, classPK, keywords);
    }

    /**
     * Returns the number of structures matching the groups and class name IDs,
     * and matching the keywords in the structure names and descriptions.
     *
     * @param  companyId the primary key of the structure's company
     * @param  groupIds the primary keys of the groups
     * @param  classNameId the primary key of the class name of the model the
     *         structure is related to
     * @param  keywords the keywords (space separated), which may occur in the
     *         structure's name or description (optionally <code>null</code>)
     * @return the number of matching structures
     */
    @Override
    public int searchCount(long companyId, long[] groupIds, long classNameId, String keywords, int status) {

        return ddmStructureFinder.countByKeywords(companyId, groupIds, classNameId, keywords, status);
    }

    /**
     * Returns the number of structures matching the groups, class name IDs,
     * name keyword, description keyword, storage type, and type
     *
     * @param  companyId the primary key of the structure's company
     * @param  groupIds the primary keys of the groups
     * @param  classNameId
     * @param  name the name keywords
     * @param  description the description keywords
     * @param  storageType the structure's storage type. It can be "xml" or
     *         "expando". For more information, see {@link StorageType}.
     * @param  type the structure's type. For more information, see {@link
     *         DDMStructureConstants}.
     * @param  andOperator whether every field must match its keywords, or just
     *         one field
     * @return the number of matching structures
     */
    @Override
    public int searchCount(long companyId, long[] groupIds, long classNameId, String name, String description,
            String storageType, int type, int status, boolean andOperator) {

        return ddmStructureFinder.countByC_G_C_N_D_S_T_S(companyId, groupIds, classNameId, name, description,
                storageType, type, status, andOperator);
    }

    @Override
    public DDMStructure updateStructure(long userId, long structureId, DDMForm ddmForm, DDMFormLayout ddmFormLayout,
            ServiceContext serviceContext) throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        return doUpdateStructure(userId, structure.getParentStructureId(), structure.getNameMap(),
                structure.getDescriptionMap(), ddmForm, ddmFormLayout, serviceContext, structure);
    }

    @Override
    public DDMStructure updateStructure(long userId, long groupId, long parentStructureId, long classNameId,
            String structureKey, Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, DDMForm ddmForm,
            DDMFormLayout ddmFormLayout, ServiceContext serviceContext) throws PortalException {

        structureKey = getStructureKey(structureKey);

        DDMStructure structure = ddmStructurePersistence.findByG_C_S(groupId, classNameId, structureKey);

        return doUpdateStructure(userId, parentStructureId, nameMap, descriptionMap, ddmForm, ddmFormLayout,
                serviceContext, structure);
    }

    @Override
    public DDMStructure updateStructure(long userId, long structureId, long parentStructureId,
            Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, DDMForm ddmForm,
            DDMFormLayout ddmFormLayout, ServiceContext serviceContext) throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        return doUpdateStructure(userId, parentStructureId, nameMap, descriptionMap, ddmForm, ddmFormLayout,
                serviceContext, structure);
    }

    /**
     * Updates the structure matching the class name ID, structure key, and
     * group, replacing its old parent structure, name map, description map, and
     * XSD with new ones.
     *
     * @param      groupId the primary key of the group
     * @param      parentStructureId the primary key of the new parent structure
     * @param      classNameId the primary key of the class name for the
     *             structure's related model
     * @param      structureKey the unique string identifying the structure
     * @param      nameMap the structure's new locales and localized names
     * @param      descriptionMap the structure's new locales and localized
     *             description
     * @param      definition the structure's new XML schema definition
     * @param      serviceContext the service context to be applied. Can set the
     *             structure's modification date.
     * @return     the updated structure
     * @deprecated As of 2.1.0, replaced by {@link #updateStructure(long, long,
     *             long, long, String, Map, Map, DDMForm, DDMFormLayout,
     *             ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure updateStructure(long groupId, long parentStructureId, long classNameId, String structureKey,
            Map<Locale, String> nameMap, Map<Locale, String> descriptionMap, String definition,
            ServiceContext serviceContext) throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByG_C_S(groupId, classNameId, structureKey);

        long userId = PortalUtil.getValidUserId(structure.getCompanyId(), serviceContext.getUserId());

        ddmXML.validateXML(definition);

        DDMForm ddmForm = ddmFormXSDDeserializer.deserialize(definition);

        DDMFormLayout ddmFormLayout = ddm.getDefaultDDMFormLayout(ddmForm);

        structureKey = getStructureKey(structureKey);

        return doUpdateStructure(userId, parentStructureId, nameMap, descriptionMap, ddmForm, ddmFormLayout,
                serviceContext, structure);
    }

    /**
     * Updates the structure matching the structure ID, replacing its old parent
     * structure, name map, description map, and XSD with new ones.
     *
     * @param      structureId the primary key of the structure
     * @param      parentStructureId the primary key of the new parent structure
     * @param      nameMap the structure's new locales and localized names
     * @param      descriptionMap the structure's new locales and localized
     *             descriptions
     * @param      definition the structure's new XML schema definition
     * @param      serviceContext the service context to be applied. Can set the
     *             structure's modification date.
     * @return     the updated structure
     * @deprecated As of 2.1.0, replaced by {@link #updateStructure(long, long,
     *             long, Map, Map, DDMForm, DDMFormLayout, ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure updateStructure(long structureId, long parentStructureId, Map<Locale, String> nameMap,
            Map<Locale, String> descriptionMap, String definition, ServiceContext serviceContext)
            throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        long userId = PortalUtil.getValidUserId(structure.getCompanyId(), serviceContext.getUserId());

        ddmXML.validateXML(definition);

        DDMForm ddmForm = ddmFormXSDDeserializer.deserialize(definition);

        DDMFormLayout ddmFormLayout = ddm.getDefaultDDMFormLayout(ddmForm);

        return doUpdateStructure(userId, parentStructureId, nameMap, descriptionMap, ddmForm, ddmFormLayout,
                serviceContext, structure);
    }

    /**
     * Updates the structure matching the structure ID, replacing its XSD with a
     * new one.
     *
     * @param      structureId the primary key of the structure
     * @param      definition the structure's new XML schema definition
     * @param      serviceContext the service context to be applied. Can set the
     *             structure's modification date.
     * @return     the updated structure
     * @deprecated As of 2.1.0, replaced by {@link #updateStructure(long,
     *             DDMForm, DDMFormLayout, ServiceContext)}
     */
    @Deprecated
    @Override
    public DDMStructure updateXSD(long structureId, String definition, ServiceContext serviceContext)
            throws PortalException {

        DDMStructure structure = ddmStructurePersistence.findByPrimaryKey(structureId);

        long userId = PortalUtil.getValidUserId(structure.getCompanyId(), serviceContext.getUserId());

        ddmXML.validateXML(definition);

        DDMForm ddmForm = ddmFormXSDDeserializer.deserialize(definition);

        DDMFormLayout ddmFormLayout = ddm.getDefaultDDMFormLayout(ddmForm);

        return doUpdateStructure(userId, structure.getParentStructureId(), structure.getNameMap(),
                structure.getDescriptionMap(), ddmForm, ddmFormLayout, serviceContext, structure);
    }

    protected void addDataProviderInstanceLinks(long groupId, long structureId, DDMForm ddmForm) {

        Set<Long> dataProviderInstanceIds = getDataProviderInstanceIds(groupId, ddmForm);

        for (Long dataProviderInstanceId : dataProviderInstanceIds) {
            ddmDataProviderInstanceLinkLocalService.addDataProviderInstanceLink(dataProviderInstanceId,
                    structureId);
        }
    }

    protected DDMStructureVersion addStructureVersion(User user, DDMStructure structure, String version,
            ServiceContext serviceContext) {

        long structureVersionId = counterLocalService.increment();

        DDMStructureVersion structureVersion = ddmStructureVersionPersistence.create(structureVersionId);

        structureVersion.setGroupId(structure.getGroupId());
        structureVersion.setCompanyId(structure.getCompanyId());
        structureVersion.setUserId(user.getUserId());
        structureVersion.setUserName(user.getFullName());
        structureVersion.setCreateDate(structure.getModifiedDate());
        structureVersion.setStructureId(structure.getStructureId());
        structureVersion.setVersion(version);
        structureVersion.setParentStructureId(structure.getParentStructureId());
        structureVersion.setName(structure.getName());
        structureVersion.setDescription(structure.getDescription());
        structureVersion.setDefinition(structure.getDefinition());
        structureVersion.setStorageType(structure.getStorageType());
        structureVersion.setType(structure.getType());

        int status = GetterUtil.getInteger(serviceContext.getAttribute("status"),
                WorkflowConstants.STATUS_APPROVED);

        structureVersion.setStatus(status);

        structureVersion.setStatusByUserId(user.getUserId());
        structureVersion.setStatusByUserName(user.getFullName());
        structureVersion.setStatusDate(structure.getModifiedDate());

        ddmStructureVersionPersistence.update(structureVersion);

        return structureVersion;
    }

    protected Set<Long> deleteStructures(List<DDMStructure> structures) throws PortalException {

        if (ListUtil.isEmpty(structures)) {
            return Collections.emptySet();
        }

        Set<Long> deletedStructureIds = new HashSet<>();

        for (DDMStructure structure : structures) {
            if (deletedStructureIds.contains(structure.getStructureId())) {
                continue;
            }

            if (!GroupThreadLocal.isDeleteInProcess()) {
                List<DDMStructure> childDDMStructures = ddmStructurePersistence
                        .findByParentStructureId(structure.getStructureId());

                deletedStructureIds.addAll(deleteStructures(childDDMStructures));
            }

            ddmStructureLocalService.deleteStructure(structure);

            deletedStructureIds.add(structure.getStructureId());
        }

        return deletedStructureIds;
    }

    protected DDMStructure doUpdateStructure(long userId, long parentStructureId, Map<Locale, String> nameMap,
            Map<Locale, String> descriptionMap, DDMForm ddmForm, DDMFormLayout ddmFormLayout,
            ServiceContext serviceContext, DDMStructure structure) throws PortalException {

        // Structure

        User user = userLocalService.getUser(userId);

        DDMForm parentDDMForm = getParentDDMForm(parentStructureId);

        validateParentStructure(structure.getStructureId(), parentStructureId);
        validate(nameMap, parentDDMForm, ddmForm);

        structure.setParentStructureId(parentStructureId);

        DDMStructureVersion latestStructureVersion = ddmStructureVersionLocalService
                .getLatestStructureVersion(structure.getStructureId());

        boolean majorVersion = GetterUtil.getBoolean(serviceContext.getAttribute("majorVersion"));

        String version = getNextVersion(latestStructureVersion.getVersion(), majorVersion);

        structure.setVersion(version);

        structure.setNameMap(nameMap, ddmForm.getDefaultLocale());
        structure.setVersionUserId(user.getUserId());
        structure.setVersionUserName(user.getFullName());
        structure.setDescriptionMap(descriptionMap, ddmForm.getDefaultLocale());
        structure.setDefinition(ddmFormJSONSerializer.serialize(ddmForm));

        // Structure version

        DDMStructureVersion structureVersion = addStructureVersion(user, structure, version, serviceContext);

        // Structure layout

        // Explicitly pop UUID from service context to ensure no lingering
        // values remain there from other components (e.g. Journal)

        serviceContext.getUuid();

        ddmStructureLayoutLocalService.addStructureLayout(structureVersion.getUserId(),
                structureVersion.getGroupId(), structureVersion.getStructureVersionId(), ddmFormLayout,
                serviceContext);

        if (!structureVersion.isApproved()) {
            return structure;
        }

        ddmStructurePersistence.update(structure);

        // Structure templates

        syncStructureTemplatesFields(structure);

        // Data provider instance links

        updateDataProviderInstanceLinks(structure.getGroupId(), structure.getStructureId(), ddmForm);

        // Indexer

        reindexStructure(structure, serviceContext);

        return structure;
    }

    protected Set<Long> getDataProviderInstanceIds(long groupId, DDMForm ddmForm) {

        Set<Long> dataProviderInstanceIds = new HashSet<>();

        Map<String, DDMFormField> ddmFormFieldsMap = ddmForm.getDDMFormFieldsMap(true);

        for (DDMFormField ddmFormField : ddmFormFieldsMap.values()) {
            long ddmDataProviderInstanceId = GetterUtil
                    .getLong(ddmFormField.getProperty("ddmDataProviderInstanceId"));

            if (ddmDataProviderInstanceId > 0) {
                dataProviderInstanceIds.add(ddmDataProviderInstanceId);
            }
        }

        for (DDMFormRule ddmFormRule : ddmForm.getDDMFormRules()) {
            Set<Long> ddmFormDataProviderInstanceIds = getDataProviderInstanceIds(groupId, ddmFormRule);

            dataProviderInstanceIds.addAll(ddmFormDataProviderInstanceIds);
        }

        return dataProviderInstanceIds;
    }

    protected Set<Long> getDataProviderInstanceIds(long groupId, DDMFormRule ddmFormRule) {

        List<String> actions = ddmFormRule.getActions();

        if (ListUtil.isEmpty(actions)) {
            return Collections.emptySet();
        }

        Set<Long> dataProviderInstanceIds = new HashSet<>(actions.size());

        for (String action : actions) {
            Matcher matcher = _callFunctionPattern.matcher(action);

            while (matcher.find()) {
                String dataProviderUuid = matcher.group(1);

                DDMDataProviderInstance dataProviderInstance = ddmDataProviderInstanceLocalService
                        .fetchDDMDataProviderInstanceByUuidAndGroupId(dataProviderUuid, groupId);

                if (dataProviderInstance != null) {
                    dataProviderInstanceIds.add(dataProviderInstance.getDataProviderInstanceId());
                }
            }
        }

        return dataProviderInstanceIds;
    }

    protected Set<String> getDDMFormFieldsNames(DDMForm ddmForm) {
        Map<String, DDMFormField> ddmFormFieldsMap = ddmForm.getDDMFormFieldsMap(true);

        if (MapUtil.isEmpty(ddmFormFieldsMap)) {
            return Collections.emptySet();
        }

        Set<String> ddmFormFieldsNames = new HashSet<>(ddmFormFieldsMap.size());

        for (String ddmFormFieldName : ddmFormFieldsMap.keySet()) {
            ddmFormFieldsNames.add(StringUtil.toLowerCase(ddmFormFieldName));
        }

        return ddmFormFieldsNames;
    }

    protected String getNextVersion(String version, boolean majorVersion) {
        int[] versionParts = StringUtil.split(version, StringPool.PERIOD, 0);

        if (majorVersion) {
            versionParts[0]++;
            versionParts[1] = 0;
        } else {
            versionParts[1]++;
        }

        return versionParts[0] + StringPool.PERIOD + versionParts[1];
    }

    protected DDMForm getParentDDMForm(long parentStructureId) {
        DDMStructure parentStructure = ddmStructurePersistence.fetchByPrimaryKey(parentStructureId);

        if (parentStructure == null) {
            return null;
        }

        return parentStructure.getFullHierarchyDDMForm();
    }

    protected String getStructureKey(String structureKey) {
        if (structureKey != null) {
            structureKey = structureKey.trim();

            return StringUtil.toUpperCase(structureKey);
        }

        return StringPool.BLANK;
    }

    protected List<DDMTemplate> getStructureTemplates(DDMStructure structure, String type) {

        long classNameId = classNameLocalService.getClassNameId(DDMStructure.class);

        return ddmTemplateLocalService.getTemplates(structure.getGroupId(), classNameId, structure.getStructureId(),
                type);
    }

    protected void reindexStructure(DDMStructure structure, ServiceContext serviceContext) throws PortalException {

        if (!serviceContext.isIndexingEnabled()) {
            return;
        }

        Indexer<?> indexer = IndexerRegistryUtil.nullSafeGetIndexer(structure.getClassName());

        if (!(indexer instanceof DDMStructureIndexer)) {
            return;
        }

        String backgroundTaskName = DDMStructureIndexerBackgroundTaskExecutor
                .getBackgroundTaskName(structure.getStructureId());

        Map<String, Serializable> taskContextMap = new HashMap<>();

        taskContextMap.put("structureId", structure.getStructureId());

        backgroundTaskmanager.addBackgroundTask(structure.getUserId(), structure.getGroupId(), backgroundTaskName,
                DDMStructureIndexerBackgroundTaskExecutor.class.getName(), taskContextMap, serviceContext);
    }

    protected void syncStructureTemplatesFields(final DDMStructure structure) {
        TransactionCommitCallbackUtil.registerCallback(new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                DDMFormTemplateSynchonizer ddmFormTemplateSynchonizer = new DDMFormTemplateSynchonizer(
                        structure.getDDMForm(), ddmFormJSONDeserializer, ddmFormJSONSerializer,
                        ddmTemplateLocalService);

                List<DDMTemplate> templates = getStructureTemplates(structure,
                        DDMTemplateConstants.TEMPLATE_TYPE_FORM);

                ddmFormTemplateSynchonizer.setDDMFormTemplates(templates);

                ddmFormTemplateSynchonizer.synchronize();

                return null;
            }

        });
    }

    protected void updateDataProviderInstanceLinks(long groupId, long structureId, DDMForm ddmForm) {

        Set<Long> dataProviderInstanceIds = getDataProviderInstanceIds(groupId, ddmForm);

        List<DDMDataProviderInstanceLink> dataProviderInstanceLinks = ddmDataProviderInstanceLinkLocalService
                .getDataProviderInstanceLinks(structureId);

        for (DDMDataProviderInstanceLink dataProviderInstanceLink : dataProviderInstanceLinks) {

            long dataProviderInstanceId = dataProviderInstanceLink.getDataProviderInstanceId();

            if (dataProviderInstanceIds.remove(dataProviderInstanceId)) {
                continue;
            }

            ddmDataProviderInstanceLinkLocalService.deleteDataProviderInstanceLink(dataProviderInstanceLink);
        }

        for (Long dataProviderInstanceId : dataProviderInstanceIds) {
            ddmDataProviderInstanceLinkLocalService.addDataProviderInstanceLink(dataProviderInstanceId,
                    structureId);
        }
    }

    protected void validate(DDMForm ddmForm) throws PortalException {
        ddmFormValidator.validate(ddmForm);
    }

    protected void validate(DDMForm parentDDMForm, DDMForm ddmForm) throws PortalException {

        Set<String> commonDDMFormFieldNames = SetUtil.intersect(getDDMFormFieldsNames(parentDDMForm),
                getDDMFormFieldsNames(ddmForm));

        if (!commonDDMFormFieldNames.isEmpty()) {
            throw new StructureDuplicateElementException(
                    "Duplicate DDM form field names: " + StringUtil.merge(commonDDMFormFieldNames));
        }
    }

    protected void validate(long groupId, long parentStructureId, long classNameId, String structureKey,
            Map<Locale, String> nameMap, DDMForm ddmForm) throws PortalException {

        structureKey = getStructureKey(structureKey);

        DDMStructure structure = ddmStructurePersistence.fetchByG_C_S(groupId, classNameId, structureKey);

        if (structure != null) {
            StructureDuplicateStructureKeyException sdske = new StructureDuplicateStructureKeyException();

            sdske.setStructureKey(structure.getStructureKey());

            throw sdske;
        }

        DDMForm parentDDMForm = getParentDDMForm(parentStructureId);

        validate(nameMap, parentDDMForm, ddmForm);
    }

    protected void validate(Map<Locale, String> nameMap, DDMForm parentDDMForm, DDMForm ddmForm)
            throws PortalException {

        try {
            validate(nameMap, ddmForm.getDefaultLocale());

            validate(ddmForm);

            if (parentDDMForm != null) {
                validate(parentDDMForm, ddmForm);
            }
        } catch (DDMFormValidationException ddmfve) {
            throw ddmfve;
        } catch (LocaleException le) {
            throw le;
        } catch (StructureDuplicateElementException sdee) {
            throw sdee;
        } catch (StructureNameException sne) {
            throw sne;
        } catch (StructureDefinitionException sde) {
            throw sde;
        } catch (Exception e) {
            throw new StructureDefinitionException(e);
        }
    }

    protected void validate(Map<Locale, String> nameMap, Locale contentDefaultLocale) throws PortalException {

        String name = nameMap.get(contentDefaultLocale);

        if (Validator.isNull(name)) {
            throw new StructureNameException("Name is null for locale " + contentDefaultLocale.getDisplayName());
        }

        if (!LanguageUtil.isAvailableLocale(contentDefaultLocale)) {
            Long companyId = CompanyThreadLocal.getCompanyId();

            LocaleException le = new LocaleException(LocaleException.TYPE_CONTENT,
                    StringBundler.concat("The locale ", String.valueOf(contentDefaultLocale),
                            " is not available in company ", String.valueOf(companyId)));

            le.setSourceAvailableLocales(Collections.singleton(contentDefaultLocale));
            le.setTargetAvailableLocales(LanguageUtil.getAvailableLocales());

            throw le;
        }
    }

    protected void validateParentStructure(long structureId, long parentStructureId) throws PortalException {

        while (parentStructureId != 0) {
            DDMStructure parentStructure = ddmStructurePersistence.fetchByPrimaryKey(parentStructureId);

            if (structureId == parentStructure.getStructureId()) {
                throw new InvalidParentStructureException();
            }

            parentStructureId = parentStructure.getParentStructureId();
        }
    }

    @ServiceReference(type = BackgroundTaskManager.class)
    protected BackgroundTaskManager backgroundTaskmanager;

    @ServiceReference(type = DDM.class)
    protected DDM ddm;

    @ServiceReference(type = DDMFormJSONDeserializer.class)
    protected DDMFormJSONDeserializer ddmFormJSONDeserializer;

    @ServiceReference(type = DDMFormJSONSerializer.class)
    protected DDMFormJSONSerializer ddmFormJSONSerializer;

    @ServiceReference(type = DDMFormValidator.class)
    protected DDMFormValidator ddmFormValidator;

    @ServiceReference(type = DDMFormXSDDeserializer.class)
    protected DDMFormXSDDeserializer ddmFormXSDDeserializer;

    @ServiceReference(type = DDMXML.class)
    protected DDMXML ddmXML;

    private final Pattern _callFunctionPattern = Pattern
            .compile("call\\(\\s*\'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-"
                    + "[0-9a-f]{12})\'\\s*,\\s*\'(.*)\'\\s*,\\s*\'(.*)\'\\s*\\)");

}