mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	120966 jkaabimofrad: ACE-4994: Removed the local cache to avoid issues when updating the custom model in a clustered env. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.1.N/root@120982 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
			
				
	
	
		
			1259 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			1259 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2005-2016 Alfresco Software Limited.
 | |
|  *
 | |
|  * This file is part of Alfresco
 | |
|  *
 | |
|  * Alfresco 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 3 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * Alfresco 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.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public License
 | |
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| package org.alfresco.repo.dictionary;
 | |
| 
 | |
| import java.io.ByteArrayInputStream;
 | |
| import java.io.ByteArrayOutputStream;
 | |
| import java.io.File;
 | |
| import java.io.IOException;
 | |
| import java.io.InputStream;
 | |
| import java.io.Serializable;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Collection;
 | |
| import java.util.Collections;
 | |
| import java.util.HashMap;
 | |
| import java.util.Iterator;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| 
 | |
| import javax.xml.xpath.XPath;
 | |
| import javax.xml.xpath.XPathConstants;
 | |
| import javax.xml.xpath.XPathExpression;
 | |
| import javax.xml.xpath.XPathFactory;
 | |
| 
 | |
| import org.alfresco.error.AlfrescoRuntimeException;
 | |
| import org.alfresco.model.ContentModel;
 | |
| import org.alfresco.query.EmptyPagingResults;
 | |
| import org.alfresco.query.PageDetails;
 | |
| import org.alfresco.query.PagingRequest;
 | |
| import org.alfresco.query.PagingResults;
 | |
| import org.alfresco.repo.admin.RepoAdminServiceImpl;
 | |
| import org.alfresco.repo.content.MimetypeMap;
 | |
| import org.alfresco.repo.download.DownloadModel;
 | |
| import org.alfresco.repo.download.DownloadStorage;
 | |
| import org.alfresco.repo.model.filefolder.HiddenAspect;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | |
| import org.alfresco.service.cmr.admin.RepoAdminService;
 | |
| import org.alfresco.service.cmr.dictionary.AspectDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.ClassDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.Constraint;
 | |
| import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.CustomModelDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.CustomModelException;
 | |
| import org.alfresco.service.cmr.dictionary.CustomModelService;
 | |
| import org.alfresco.service.cmr.dictionary.DictionaryService;
 | |
| import org.alfresco.service.cmr.dictionary.ModelDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.PropertyDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.TypeDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.DictionaryException.DuplicateDefinitionException;
 | |
| import org.alfresco.service.cmr.download.DownloadService;
 | |
| import org.alfresco.service.cmr.repository.ContentReader;
 | |
| import org.alfresco.service.cmr.repository.ContentService;
 | |
| import org.alfresco.service.cmr.repository.ContentWriter;
 | |
| import org.alfresco.service.cmr.repository.NodeRef;
 | |
| import org.alfresco.service.cmr.repository.NodeService;
 | |
| import org.alfresco.service.cmr.repository.StoreRef;
 | |
| import org.alfresco.service.cmr.search.SearchService;
 | |
| import org.alfresco.service.cmr.security.AuthorityService;
 | |
| import org.alfresco.service.cmr.security.PermissionService;
 | |
| import org.alfresco.service.namespace.QName;
 | |
| import org.alfresco.util.Pair;
 | |
| import org.alfresco.util.ParameterCheck;
 | |
| import org.alfresco.util.PropertyCheck;
 | |
| import org.alfresco.util.TempFileProvider;
 | |
| import org.alfresco.util.XMLUtil;
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| import org.w3c.dom.Document;
 | |
| import org.w3c.dom.Node;
 | |
| 
 | |
| /**
 | |
|  * Custom Model Service Implementation
 | |
|  *
 | |
|  * @author Jamal Kaabi-Mofrad
 | |
|  */
 | |
| public class CustomModelServiceImpl implements CustomModelService
 | |
| {
 | |
|     private static final Log logger = LogFactory.getLog(CustomModelServiceImpl.class);
 | |
| 
 | |
|     public static final String DEFAULT_CUSTOM_MODEL_ASPECT = "hasAspect('cmm:customModelManagement')";
 | |
| 
 | |
|     public static final QName ASPECT_CUSTOM_MODEL = QName.createQName("http://www.alfresco.org/model/custommodelmanagement/1.0", "customModelManagement");
 | |
| 
 | |
|     public static final String ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY = "ALFRESCO_MODEL_ADMINISTRATORS";
 | |
|     public static final String GROUP_ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY = PermissionService.GROUP_PREFIX
 | |
|                 + ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY;
 | |
| 
 | |
|     public static final String SHARE_EXT_MODULE_SUFFIX = "_module.xml";
 | |
| 
 | |
|     /** Messages */
 | |
|     private static final String MSG_NAME_ALREADY_IN_USE = "cmm.service.name_already_in_use";
 | |
|     private static final String MSG_CREATE_MODEL_ERR = "cmm.service.create_model_err";
 | |
|     private static final String MSG_UPDATE_MODEL_ERR = "cmm.service.update_model_err";
 | |
|     private static final String MSG_MULTIPLE_MODELS = "cmm.service.multiple_models";
 | |
|     private static final String MSG_RETRIEVE_MODEL = "cmm.service.retrieve_model";
 | |
|     private static final String MSG_MODEL_NOT_EXISTS = "cmm.service.model_not_exists";
 | |
|     private static final String MSG_NAMESPACE_NOT_EXISTS = "cmm.service.namespace_not_exists";
 | |
|     private static final String MSG_NAMESPACE_MANY_EXIST = "cmm.service.namespace_many_exist";
 | |
|     private static final String MSG_NAMESPACE_URI_ALREADY_IN_USE = "cmm.service.namespace_uri_already_in_use";
 | |
|     private static final String MSG_NAMESPACE_PREFIX_ALREADY_IN_USE = "cmm.service.namespace_prefix_already_in_use";
 | |
|     private static final String MSG_UNABLE_DELETE_ACTIVE_MODEL = "cmm.service.unable_delete_active_model";
 | |
|     private static final String MSG_UNABLE_MODEL_DELETE = "cmm.service.unable_model_delete";
 | |
|     private static final String MSG_UNABLE_MODEL_DEACTIVATE = "cmm.service.unable_model_deactivate";
 | |
|     private static final String MSG_UNABLE_MODEL_ACTIVATE = "cmm.service.unable_model_activate";
 | |
|     private static final String MSG_INVALID_MODEL = "cmm.service.invalid_model";
 | |
|     private static final String MSG_NAMESPACE_ACTIVE_MODEL = "cmm.service.namespace_active_model";
 | |
|     private static final String MSG_FAILED_DEACTIVATION_TYPE_DEPENDENCY = "cmm.service.failed.deactivation.type.dependency";
 | |
|     private static final String MSG_FAILED_DEACTIVATION_ASPECT_DEPENDENCY = "cmm.service.failed.deactivation.aspect.dependency";
 | |
|     private static final String MSG_DOWNLOAD_COPY_MODEL_ERR = "cmm.service.download.create_model_copy_err";
 | |
|     private static final String MSG_DOWNLOAD_CREATE_SHARE_EXT_ERR = "cmm.service.download.create_share_ext_err";
 | |
| 
 | |
|     private NodeService nodeService;
 | |
|     private DictionaryDAOImpl dictionaryDAO;
 | |
|     private ContentService contentService;
 | |
|     private SearchService searchService;
 | |
|     private RepositoryLocation repoModelsLocation;
 | |
|     private NamespaceDAO namespaceDAO;
 | |
|     private DictionaryService dictionaryService;
 | |
|     private RetryingTransactionHelper retryingTransactionHelper;
 | |
|     private RepoAdminService repoAdminService;
 | |
|     private AuthorityService authorityService;
 | |
|     private HiddenAspect hiddenAspect;
 | |
|     private DownloadService downloadService;
 | |
|     private DownloadStorage downloadStorage;
 | |
| 
 | |
|     private String shareExtModulePath;
 | |
| 
 | |
| 
 | |
|     public void setNodeService(NodeService nodeService)
 | |
|     {
 | |
|         this.nodeService = nodeService;
 | |
|     }
 | |
| 
 | |
|     public void setDictionaryDAO(DictionaryDAOImpl dictionaryDAO)
 | |
|     {
 | |
|         this.dictionaryDAO = dictionaryDAO;
 | |
|     }
 | |
| 
 | |
|     public void setContentService(ContentService contentService)
 | |
|     {
 | |
|         this.contentService = contentService;
 | |
|     }
 | |
| 
 | |
|     public void setSearchService(SearchService searchService)
 | |
|     {
 | |
|         this.searchService = searchService;
 | |
|     }
 | |
| 
 | |
|     public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation)
 | |
|     {
 | |
|         this.repoModelsLocation = repoModelsLocation;
 | |
|     }
 | |
| 
 | |
|     public void setDictionaryService(DictionaryService dictionaryService)
 | |
|     {
 | |
|         this.dictionaryService = dictionaryService;
 | |
|     }
 | |
| 
 | |
|     public void setNamespaceDAO(NamespaceDAO namespaceDAO)
 | |
|     {
 | |
|         this.namespaceDAO = namespaceDAO;
 | |
|     }
 | |
| 
 | |
|     public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
 | |
|     {
 | |
|         this.retryingTransactionHelper = retryingTransactionHelper;
 | |
|     }
 | |
| 
 | |
|     public void setRepoAdminService(RepoAdminService repoAdminService)
 | |
|     {
 | |
|         this.repoAdminService = repoAdminService;
 | |
|     }
 | |
| 
 | |
|     public void setAuthorityService(AuthorityService authorityService)
 | |
|     {
 | |
|         this.authorityService = authorityService;
 | |
|     }
 | |
| 
 | |
|     public void setHiddenAspect(HiddenAspect hiddenAspect)
 | |
|     {
 | |
|         this.hiddenAspect = hiddenAspect;
 | |
|     }
 | |
| 
 | |
|     public void setDownloadService(DownloadService downloadSerivce)
 | |
|     {
 | |
|         this.downloadService = downloadSerivce;
 | |
|     }
 | |
| 
 | |
|     public void setDownloadStorage(DownloadStorage downloadStorage)
 | |
|     {
 | |
|         this.downloadStorage = downloadStorage;
 | |
|     }
 | |
| 
 | |
|     public void setShareExtModulePath(String shareExtModulePath)
 | |
|     {
 | |
|         this.shareExtModulePath = shareExtModulePath;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Checks that all necessary properties and services have been provided.
 | |
|      */
 | |
|     public void init()
 | |
|     {
 | |
|         PropertyCheck.mandatory(this, "nodeService", nodeService);
 | |
|         PropertyCheck.mandatory(this, "dictionaryDAO", dictionaryDAO);
 | |
|         PropertyCheck.mandatory(this, "contentService", contentService);
 | |
|         PropertyCheck.mandatory(this, "searchService", searchService);
 | |
|         PropertyCheck.mandatory(this, "repoModelsLocation", repoModelsLocation);
 | |
|         PropertyCheck.mandatory(this, "namespaceDAO", namespaceDAO);
 | |
|         PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
 | |
|         PropertyCheck.mandatory(this, "retryingTransactionHelper", retryingTransactionHelper);
 | |
|         PropertyCheck.mandatory(this, "repoAdminService", repoAdminService);
 | |
|         PropertyCheck.mandatory(this, "authorityService", authorityService);
 | |
|         PropertyCheck.mandatory(this, "hiddenAspect", hiddenAspect);
 | |
|         PropertyCheck.mandatory(this, "shareExtModulePath", shareExtModulePath);
 | |
|         PropertyCheck.mandatory(this, "downloadService", downloadService);
 | |
|         PropertyCheck.mandatory(this, "downloadStorage", downloadStorage);
 | |
|     }
 | |
| 
 | |
|     private NodeRef getRootNode()
 | |
|     {
 | |
|         StoreRef storeRef = repoModelsLocation.getStoreRef();
 | |
|         return nodeService.getRootNode(storeRef);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public NodeRef getModelNodeRef(String modelName)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("modelName", modelName);
 | |
| 
 | |
|         StringBuilder builder = new StringBuilder(120);
 | |
|         builder.append(repoModelsLocation.getPath()).append("//.[@cm:name='").append(modelName).append("' and ")
 | |
|                     .append(RepoAdminServiceImpl.defaultSubtypeOfDictionaryModel).append(']');
 | |
| 
 | |
|         List<NodeRef> nodeRefs = searchService.selectNodes(getRootNode(), builder.toString(), null, namespaceDAO, false);
 | |
| 
 | |
|         if (nodeRefs.size() == 0)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         else if (nodeRefs.size() > 1)
 | |
|         {
 | |
|             // unexpected: should not find multiple nodes with same name
 | |
|             throw new CustomModelException(MSG_MULTIPLE_MODELS, new Object[] { modelName });
 | |
|         }
 | |
| 
 | |
|         return nodeRefs.get(0);
 | |
|     }
 | |
| 
 | |
|     private M2Model getM2Model(final NodeRef modelNodeRef)
 | |
|     {
 | |
|         ContentReader reader = contentService.getReader(modelNodeRef, ContentModel.PROP_CONTENT);
 | |
|         if (reader == null)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         InputStream in = reader.getContentInputStream();
 | |
|         try
 | |
|         {
 | |
|             return M2Model.createModel(in);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             if (in != null)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     in.close();
 | |
|                 }
 | |
|                 catch (IOException e)
 | |
|                 {
 | |
|                     logger.error("Failed to close input stream for " + modelNodeRef);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public CustomModelDefinition getCustomModel(String modelName)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("modelName", modelName);
 | |
| 
 | |
|         Pair<CompiledModel, Boolean> compiledModelPair = getCustomCompiledModel(modelName);
 | |
|         CustomModelDefinition result = (compiledModelPair == null) ? null : new CustomModelDefinitionImpl(
 | |
|                     compiledModelPair.getFirst(), compiledModelPair.getSecond(), dictionaryService);
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public ModelDefinition getCustomModelByUri(String namespaceUri)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("namespaceUri", namespaceUri);
 | |
|         CompiledModel compiledModel = getModelByUri(namespaceUri);
 | |
| 
 | |
|         if (compiledModel != null)
 | |
|         {
 | |
|             return compiledModel.getModelDefinition();
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     private CompiledModel getModelByUri(String uri)
 | |
|     {
 | |
|         for (CompiledModel model : getAllCustomM2Models(false))
 | |
|         {
 | |
|             if (uri.equals(getModelNamespaceUriPrefix(model.getM2Model()).getFirst()))
 | |
|             {
 | |
|                 return model;
 | |
|             }
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns compiled custom model and whether the model is active or not as a {@code Pair} object
 | |
|      *
 | |
|      * @param modelName the name of the custom model to retrieve
 | |
|      * @return the {@code Pair<CompiledModel, Boolean>} (or null, if it doesn't exist)
 | |
|      */
 | |
|     protected Pair<CompiledModel, Boolean> getCustomCompiledModel(String modelName)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("modelName", modelName);
 | |
| 
 | |
|         final NodeRef modelNodeRef = getModelNodeRef(modelName);
 | |
| 
 | |
|         if (modelNodeRef == null || !nodeService.exists(modelNodeRef))
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         M2Model model = null;
 | |
|         final boolean isActive = Boolean.TRUE.equals(nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE));
 | |
|         if (isActive)
 | |
|         {
 | |
|             QName modelQName = (QName) nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME);
 | |
|             if (modelQName == null)
 | |
|             {
 | |
|                 return null;
 | |
|             }
 | |
|             try
 | |
|             {
 | |
|                 CompiledModel compiledModel = dictionaryDAO.getCompiledModel(modelQName);
 | |
|                 model = compiledModel.getM2Model();
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 throw new CustomModelException(MSG_RETRIEVE_MODEL, new Object[] { modelName }, e);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             model = getM2Model(modelNodeRef);
 | |
|         }
 | |
| 
 | |
|         Pair<CompiledModel, Boolean> result = (model == null) ? null : new Pair<>(compileModel(model), isActive);
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public PagingResults<CustomModelDefinition> getCustomModels(PagingRequest pagingRequest)
 | |
|     {
 | |
|         ParameterCheck.mandatory("pagingRequest", pagingRequest);
 | |
| 
 | |
|         List<CustomModelDefinition> result = getAllCustomModels();
 | |
|         return result.isEmpty() ? new EmptyPagingResults<CustomModelDefinition>() : wrapResult(pagingRequest, result);
 | |
|     }
 | |
| 
 | |
|     protected List<CustomModelDefinition> getAllCustomModels()
 | |
|     {
 | |
|         List<CustomModelDefinition> result = new ArrayList<>();
 | |
| 
 | |
|         Collection<QName> models = dictionaryDAO.getModels(true);
 | |
| 
 | |
|         List<String> dictionaryModels = new ArrayList<>();
 | |
|         for (QName model : models)
 | |
|         {
 | |
|             dictionaryModels.add(model.toPrefixString());
 | |
|         }
 | |
| 
 | |
|         List<CompiledModel> compiledModels = getAllCustomM2Models(false);
 | |
|         if (compiledModels.size() > 0)
 | |
|         {
 | |
|             for (CompiledModel model : compiledModels)
 | |
|             {
 | |
|                 // check against models loaded in dictionary
 | |
|                 boolean isActive = false;
 | |
|                 if (dictionaryModels.contains(model.getM2Model().getName()))
 | |
|                 {
 | |
|                     isActive = true;
 | |
|                 }
 | |
|                 result.add(new CustomModelDefinitionImpl(model, isActive, dictionaryService));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     private List<CompiledModel> getAllCustomM2Models(boolean onlyInactiveModels)
 | |
|     {
 | |
|         List<CompiledModel> result = new ArrayList<>();
 | |
| 
 | |
|         StringBuilder builder = new StringBuilder(160);
 | |
|         builder.append(repoModelsLocation.getPath()).append(RepoAdminServiceImpl.CRITERIA_ALL).append("[(")
 | |
|                     .append(RepoAdminServiceImpl.defaultSubtypeOfDictionaryModel).append(" and ").append(DEFAULT_CUSTOM_MODEL_ASPECT);
 | |
|         if (onlyInactiveModels)
 | |
|         {
 | |
|             builder.append(" and @cm:modelActive='false'");
 | |
|         }
 | |
|         builder.append(")]");
 | |
| 
 | |
|         List<NodeRef> nodeRefs = searchService.selectNodes(getRootNode(), builder.toString(), null, namespaceDAO, false,
 | |
|                     SearchService.LANGUAGE_XPATH);
 | |
| 
 | |
|         if (nodeRefs.size() > 0)
 | |
|         {
 | |
|             for (NodeRef nodeRef : nodeRefs)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     M2Model m2Model = getM2Model(nodeRef);
 | |
|                     if (m2Model == null)
 | |
|                     {
 | |
|                         logger.warn("Couldn't construct M2Model from nodeRef:" + nodeRef);
 | |
|                         continue;
 | |
|                     }
 | |
|                     result.add(compileModel(m2Model));
 | |
|                 }
 | |
|                 catch (Throwable t)
 | |
|                 {
 | |
|                     logger.warn("Skip model (" + t.getMessage() + ")");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public AspectDefinition getCustomAspect(QName name)
 | |
|     {
 | |
|         ParameterCheck.mandatory("name", name);
 | |
| 
 | |
|         CompiledModel compiledModel = getModelByUri(name.getNamespaceURI());
 | |
|         if (compiledModel != null)
 | |
|         {
 | |
|             return compiledModel.getAspect(name);
 | |
|         }
 | |
| 
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public PagingResults<AspectDefinition> getAllCustomAspects(PagingRequest pagingRequest)
 | |
|     {
 | |
|         ParameterCheck.mandatory("pagingRequest", pagingRequest);
 | |
| 
 | |
|         List<AspectDefinition> result = new ArrayList<>();
 | |
|         List<CompiledModel> list = getAllCustomM2Models(false);
 | |
|         for (CompiledModel model : list)
 | |
|         {
 | |
|             result.addAll(model.getAspects());
 | |
|         }
 | |
|         return wrapResult(pagingRequest, result);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public TypeDefinition getCustomType(QName name)
 | |
|     {
 | |
|         ParameterCheck.mandatory("name", name);
 | |
| 
 | |
|         CompiledModel compiledModel = getModelByUri(name.getNamespaceURI());
 | |
|         if (compiledModel != null)
 | |
|         {
 | |
|             return compiledModel.getType(name);
 | |
|         }
 | |
| 
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public PagingResults<TypeDefinition> getAllCustomTypes(PagingRequest pagingRequest)
 | |
|     {
 | |
|         ParameterCheck.mandatory("pagingRequest", pagingRequest);
 | |
| 
 | |
|         List<TypeDefinition> result = new ArrayList<>();
 | |
|         List<CompiledModel> list = getAllCustomM2Models(false);
 | |
|         for (CompiledModel model : list)
 | |
|         {
 | |
|             result.addAll(model.getTypes());
 | |
|         }
 | |
|         return wrapResult(pagingRequest, result);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public ConstraintDefinition getCustomConstraint(QName name)
 | |
|     {
 | |
|         ParameterCheck.mandatory("name", name);
 | |
| 
 | |
|         CompiledModel compiledModel = getModelByUri(name.getNamespaceURI());
 | |
|         if (compiledModel != null)
 | |
|         {
 | |
|             return compiledModel.getConstraint(name);
 | |
|         }
 | |
| 
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public CustomModelDefinition createCustomModel(M2Model m2Model, boolean activate)
 | |
|     {
 | |
|         ParameterCheck.mandatory("m2Model", m2Model);
 | |
| 
 | |
|         String modelName = m2Model.getName();
 | |
|         int colonIndex = modelName.indexOf(QName.NAMESPACE_PREFIX);
 | |
|         final String modelFileName = (colonIndex == -1) ? modelName : modelName.substring(colonIndex + 1);
 | |
| 
 | |
|         if (isModelExists(modelFileName))
 | |
|         {
 | |
|             throw new CustomModelException.ModelExistsException(MSG_NAME_ALREADY_IN_USE, new Object[] { modelFileName });
 | |
|         }
 | |
| 
 | |
|         // Validate the model namespace URI
 | |
|         validateModelNamespaceUri(getModelNamespaceUriPrefix(m2Model).getFirst());
 | |
|         // Validate the model namespace prefix
 | |
|         validateModelNamespacePrefix(getModelNamespaceUriPrefix(m2Model).getSecond());
 | |
| 
 | |
|         // Return the created model definition
 | |
|         CompiledModel compiledModel = createUpdateModel(modelFileName, m2Model, activate, MSG_CREATE_MODEL_ERR, false);
 | |
|         CustomModelDefinition modelDef = new CustomModelDefinitionImpl(compiledModel, activate, dictionaryService);
 | |
| 
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug(modelFileName + " model has been created.");
 | |
|         }
 | |
|         return modelDef;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public CustomModelDefinition updateCustomModel(String modelFileName, M2Model m2Model, boolean activate)
 | |
|     {
 | |
|         ParameterCheck.mandatory("m2Model", m2Model);
 | |
| 
 | |
|         final NodeRef existingModelNodeRef = getModelNodeRef(modelFileName);
 | |
|         if (existingModelNodeRef == null)
 | |
|         {
 | |
|             throw new CustomModelException.ModelDoesNotExistException(MSG_MODEL_NOT_EXISTS, new Object[] { modelFileName });
 | |
|         }
 | |
|         // Existing model property and namespace uri-prefix pair
 | |
|         final boolean isActive = Boolean.TRUE.equals(nodeService.getProperty(existingModelNodeRef, ContentModel.PROP_MODEL_ACTIVE));
 | |
|         final M2Model existingModel = getM2Model(existingModelNodeRef);
 | |
|         final Pair<String, String> existingNamespacePair = getModelNamespaceUriPrefix(existingModel);
 | |
|         // New model namespace uri-prefix pair
 | |
|         final Pair<String, String> newNamespacePair = getModelNamespaceUriPrefix(m2Model);
 | |
| 
 | |
|         if (isActive && !(existingNamespacePair.equals(newNamespacePair)))
 | |
|         {
 | |
|             throw new CustomModelException.ActiveModelConstraintException(MSG_NAMESPACE_ACTIVE_MODEL);
 | |
|         }
 | |
| 
 | |
|         // if the prefix has changed, then check the new prefix is not in use.
 | |
|         if (!existingNamespacePair.getSecond().equals(newNamespacePair.getSecond()))
 | |
|         {
 | |
|             validateModelNamespacePrefix(newNamespacePair.getSecond());
 | |
|         }
 | |
| 
 | |
|         // if the URI has changed, then check the new URI is not in use.
 | |
|         if (!existingNamespacePair.getFirst().equals(newNamespacePair.getFirst()))
 | |
|         {
 | |
|             validateModelNamespaceUri(newNamespacePair.getFirst());
 | |
|         }
 | |
| 
 | |
|         /*
 | |
|          * We set the requiresNewTx = true, in order to catch any exception
 | |
|          * thrown within the low level content model management.
 | |
|          * For example, deleting a property of an active model, where the
 | |
|          * property has been applied to a node will cause the
 | |
|          * ModelValidatorImpl to throw an exception.
 | |
|          * Without starting a new TX, we can't catch that exception.
 | |
|          */
 | |
|         CompiledModel compiledModel = createUpdateModel(modelFileName, m2Model, activate, MSG_UPDATE_MODEL_ERR, true);
 | |
|         CustomModelDefinition modelDef = new CustomModelDefinitionImpl(compiledModel, activate, dictionaryService);
 | |
| 
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug(modelFileName + " model has been updated.");
 | |
|         }
 | |
|         return modelDef;
 | |
|     }
 | |
| 
 | |
|     private CompiledModel createUpdateModel(final String modelFileName, final M2Model m2Model, final boolean activate, String errMsgId, boolean requiresNewTx)
 | |
|     {
 | |
|         // Validate model
 | |
|         CompiledModel compiledModel = compileModel(m2Model);
 | |
| 
 | |
|         // Validate properties default values
 | |
|         validatePropsDefaultValues(compiledModel);
 | |
| 
 | |
|         ByteArrayOutputStream xml = new ByteArrayOutputStream();
 | |
|         m2Model.toXML(xml);
 | |
|         final InputStream modelStream = new ByteArrayInputStream(xml.toByteArray());
 | |
| 
 | |
|         // Create the model node
 | |
|         NodeRef nodeRef = doInTransaction(errMsgId, requiresNewTx, new RetryingTransactionCallback<NodeRef>()
 | |
|         {
 | |
|             public NodeRef execute() throws Exception
 | |
|             {
 | |
|                 return repoAdminService.deployModel(modelStream, modelFileName, activate);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         if (!nodeService.hasAspect(nodeRef, ASPECT_CUSTOM_MODEL))
 | |
|         {
 | |
|             // Add the 'customModelManagement' marker aspect, to
 | |
|             // indicate that this model has been created dynamically by this service
 | |
|             nodeService.addAspect(nodeRef, ASPECT_CUSTOM_MODEL, null);
 | |
|         }
 | |
|         // Add hidden aspect
 | |
|         if (!hiddenAspect.hasHiddenAspect(nodeRef))
 | |
|         {
 | |
|             hiddenAspect.hideNode(nodeRef, false, false, false);
 | |
|         }
 | |
| 
 | |
|         return compiledModel;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Validates the properties' non-null default values against the defined property constraints.
 | |
|      *
 | |
|      * @param compiledModel the compiled model
 | |
|      * @throws CustomModelException.CustomModelConstraintException if there is constraint evaluation
 | |
|      *                                                             exception
 | |
|      */
 | |
|     private void validatePropsDefaultValues(CompiledModel compiledModel)
 | |
|     {
 | |
|         for (PropertyDefinition propertyDef : compiledModel.getProperties())
 | |
|         {
 | |
|             if (propertyDef.getDefaultValue() != null && propertyDef.getConstraints().size() > 0)
 | |
|             {
 | |
|                 for (ConstraintDefinition constraintDef : propertyDef.getConstraints())
 | |
|                 {
 | |
|                     Constraint constraint = constraintDef.getConstraint();
 | |
|                     try
 | |
|                     {
 | |
|                         constraint.evaluate(propertyDef.getDefaultValue());
 | |
|                     }
 | |
|                     catch (AlfrescoRuntimeException ex)
 | |
|                     {
 | |
|                         String message = getRootCauseMsg(ex, false, "cmm.service.constraint.default_prop_value_err");
 | |
|                         throw new CustomModelException.CustomModelConstraintException(message);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public CompiledModel compileModel(M2Model m2Model)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             // Validate model dependencies, constraints and etc. before creating a node
 | |
|             return m2Model.compile(dictionaryDAO, namespaceDAO, true);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             AlfrescoRuntimeException alf = null;
 | |
|             if (ex instanceof AlfrescoRuntimeException)
 | |
|             {
 | |
|                 alf = (AlfrescoRuntimeException) ex;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 alf = AlfrescoRuntimeException.create(ex, ex.getMessage());
 | |
|             }
 | |
| 
 | |
|             Throwable cause = alf.getRootCause();
 | |
|             String message = null;
 | |
| 
 | |
|             if (cause instanceof DuplicateDefinitionException)
 | |
|             {
 | |
|                 message = getRootCauseMsg(cause, false, MSG_INVALID_MODEL);
 | |
|                 throw new CustomModelException.CustomModelConstraintException(message);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 message = getRootCauseMsg(cause, true, null);
 | |
|                 throw new CustomModelException.InvalidCustomModelException(MSG_INVALID_MODEL, new Object[] { message }, ex);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected <T> PagingResults<T> wrapResult(PagingRequest pagingRequest, List<T> result)
 | |
|     {
 | |
|         final int totalSize = result.size();
 | |
|         final PageDetails pageDetails = PageDetails.getPageDetails(pagingRequest, totalSize);
 | |
| 
 | |
|         final List<T> page = new ArrayList<>(pageDetails.getPageSize());
 | |
|         Iterator<T> it = result.iterator();
 | |
|         for (int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++)
 | |
|         {
 | |
|             T element = it.next();
 | |
|             if (counter < pageDetails.getSkipCount())
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
|             if (counter > pageDetails.getEnd() - 1)
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|             page.add(element);
 | |
|         }
 | |
| 
 | |
|         return new PagingResults<T>()
 | |
|         {
 | |
|             @Override
 | |
|             public List<T> getPage()
 | |
|             {
 | |
|                 return page;
 | |
|             }
 | |
| 
 | |
|             @Override
 | |
|             public boolean hasMoreItems()
 | |
|             {
 | |
|                 return pageDetails.hasMoreItems();
 | |
|             }
 | |
| 
 | |
|             @Override
 | |
|             public Pair<Integer, Integer> getTotalResultCount()
 | |
|             {
 | |
|                 Integer total = Integer.valueOf(totalSize);
 | |
|                 return new Pair<>(total, total);
 | |
|             }
 | |
| 
 | |
|             @Override
 | |
|             public String getQueryExecutionId()
 | |
|             {
 | |
|                 return null;
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public boolean isModelAdmin(String userName)
 | |
|     {
 | |
|         if (userName == null)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         return this.authorityService.isAdminAuthority(userName)
 | |
|                     || this.authorityService.getAuthoritiesForUser(userName).contains(GROUP_ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void activateCustomModel(String modelName)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             repoAdminService.activateModel(modelName);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             throw new CustomModelException(MSG_UNABLE_MODEL_ACTIVATE, new Object[] { modelName }, ex);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void deactivateCustomModel(final String modelName)
 | |
|     {
 | |
|         CustomModelDefinition customModelDefinition = getCustomModel(modelName);
 | |
|         if (customModelDefinition == null)
 | |
|         {
 | |
|             throw new CustomModelException.ModelDoesNotExistException(MSG_MODEL_NOT_EXISTS, new Object[] { modelName });
 | |
|         }
 | |
| 
 | |
|         Collection<TypeDefinition> modelTypes = customModelDefinition.getTypeDefinitions();
 | |
|         Collection<AspectDefinition> modelAspects = customModelDefinition.getAspectDefinitions();
 | |
| 
 | |
|         for (CompiledModel cm : getAllCustomM2Models(false))
 | |
|         {
 | |
|             // Ignore type/aspect dependency check within the model itself
 | |
|             if (!customModelDefinition.getName().equals(cm.getModelDefinition().getName()))
 | |
|             {
 | |
|                 // Check if the type of the model being deactivated is the parent of another model's type
 | |
|                 validateTypeAspectDependency(modelTypes, cm.getTypes());
 | |
| 
 | |
|                 // Check if the aspect of the model being deactivated is the parent of another model's aspect
 | |
|                 validateTypeAspectDependency(modelAspects, cm.getAspects());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // requiresNewTx = true, in order to catch any exception thrown within
 | |
|         // "DictionaryModelType$DictionaryModelTypeTransactionListener" model validation.
 | |
|         doInTransaction(MSG_UNABLE_MODEL_DEACTIVATE, true, new RetryingTransactionCallback<Void>()
 | |
|         {
 | |
|             public Void execute() throws Exception
 | |
|             {
 | |
|                 repoAdminService.deactivateModel(modelName);
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     private void validateTypeAspectDependency(Collection<? extends ClassDefinition> parentDefs, Collection<? extends ClassDefinition> childDefs)
 | |
|     {
 | |
|         for (ClassDefinition parentClassDef : parentDefs)
 | |
|         {
 | |
|             for (ClassDefinition childClassDef : childDefs)
 | |
|             {
 | |
|                 if (parentClassDef.getName().equals(childClassDef.getParentName()))
 | |
|                 {
 | |
|                     Object[] msgParams = new Object[] { parentClassDef.getName().toPrefixString(),
 | |
|                                 childClassDef.getName().toPrefixString(),
 | |
|                                 childClassDef.getModel().getName().getLocalName() };
 | |
| 
 | |
|                     if (parentClassDef instanceof TypeDefinition)
 | |
|                     {
 | |
|                         throw new CustomModelException.CustomModelConstraintException(MSG_FAILED_DEACTIVATION_TYPE_DEPENDENCY, msgParams);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         throw new CustomModelException.CustomModelConstraintException(MSG_FAILED_DEACTIVATION_ASPECT_DEPENDENCY, msgParams);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void deleteCustomModel(String modelName)
 | |
|     {
 | |
|         NodeRef nodeRef = getModelNodeRef(modelName);
 | |
|         if (nodeRef == null)
 | |
|         {
 | |
|             throw new CustomModelException.ModelDoesNotExistException(MSG_MODEL_NOT_EXISTS, new Object[] { modelName });
 | |
|         }
 | |
| 
 | |
|         final boolean isActive = Boolean.TRUE.equals(nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE));
 | |
|         if (isActive)
 | |
|         {
 | |
|             throw new CustomModelException.ActiveModelConstraintException(MSG_UNABLE_DELETE_ACTIVE_MODEL);
 | |
|         }
 | |
|         try
 | |
|         {
 | |
|             repoAdminService.undeployModel(modelName);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             throw new CustomModelException(MSG_UNABLE_MODEL_DELETE, new Object[] { modelName }, ex);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public boolean isNamespaceUriExists(String modelNamespaceUri)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("modelNamespaceUri", modelNamespaceUri);
 | |
| 
 | |
|         Collection<String> uris = namespaceDAO.getURIs();
 | |
|         if (uris.contains(modelNamespaceUri))
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         for (CompiledModel model : getAllCustomM2Models(false))
 | |
|         {
 | |
|             if (modelNamespaceUri.equals(getModelNamespaceUriPrefix(model.getM2Model()).getFirst()))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public boolean isNamespacePrefixExists(String modelNamespacePrefix)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("modelNamespacePrefix", modelNamespacePrefix);
 | |
| 
 | |
|         Collection<String> prefixes = namespaceDAO.getPrefixes();
 | |
|         if (prefixes.contains(modelNamespacePrefix))
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         for (CompiledModel model : getAllCustomM2Models(false))
 | |
|         {
 | |
|             if (modelNamespacePrefix.equals(getModelNamespaceUriPrefix(model.getM2Model()).getSecond()))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public boolean isModelExists(String modelFileName)
 | |
|     {
 | |
|         NodeRef nodeRef = getModelNodeRef(modelFileName);
 | |
|         if (nodeRef != null)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|         // Also check against the bootstrapped models
 | |
|         for (QName qname : dictionaryService.getAllModels())
 | |
|         {
 | |
|             if (qname.getLocalName().equalsIgnoreCase(modelFileName))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     private Pair<String, String> getModelNamespaceUriPrefix(M2Model model)
 | |
|     {
 | |
|         ParameterCheck.mandatory("model", model);
 | |
| 
 | |
|         List<M2Namespace> namespaces = model.getNamespaces();
 | |
|         if (namespaces.isEmpty())
 | |
|         {
 | |
|             throw new CustomModelException.InvalidNamespaceException(MSG_NAMESPACE_NOT_EXISTS, new Object[] { model.getName() });
 | |
|         }
 | |
|         if (namespaces.size() > 1)
 | |
|         {
 | |
|             throw new CustomModelException.InvalidNamespaceException(MSG_NAMESPACE_MANY_EXIST, new Object[] { model.getName() });
 | |
|         }
 | |
|         M2Namespace ns = namespaces.iterator().next();
 | |
| 
 | |
|         return new Pair<>(ns.getUri(), ns.getPrefix());
 | |
|     }
 | |
| 
 | |
|     private void validateModelNamespaceUri(String uri)
 | |
|     {
 | |
|         if (isNamespaceUriExists(uri))
 | |
|         {
 | |
|             throw new CustomModelException.NamespaceConstraintException(MSG_NAMESPACE_URI_ALREADY_IN_USE, new Object[] { uri });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private void validateModelNamespacePrefix(String prefix)
 | |
|     {
 | |
|         if (isNamespacePrefixExists(prefix))
 | |
|         {
 | |
|             throw new CustomModelException.NamespaceConstraintException(MSG_NAMESPACE_PREFIX_ALREADY_IN_USE, new Object[] { prefix });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * A helper method to run a unit of work in a transaction.
 | |
|      *
 | |
|      * @param errMsgId message id for the new wrapper exception ({@link CustomModelException})
 | |
|      *            when an exception occurs
 | |
|      * @param requiresNewTx <tt>true</tt> to force a new transaction or
 | |
|      *            <tt>false</tt> to partake in any existing transaction
 | |
|      * @param cb The callback containing the unit of work
 | |
|      * @return Returns the result of the unit of work
 | |
|      */
 | |
|     private <R> R doInTransaction(String errMsgId, boolean requiresNewTx, RetryingTransactionCallback<R> cb)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             return retryingTransactionHelper.doInTransaction(cb, false, requiresNewTx);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             AlfrescoRuntimeException alf = null;
 | |
|             if (ex instanceof AlfrescoRuntimeException)
 | |
|             {
 | |
|                 alf = (AlfrescoRuntimeException) ex;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 alf = AlfrescoRuntimeException.create(ex, ex.getMessage());
 | |
|             }
 | |
| 
 | |
|             Throwable cause = alf.getRootCause();
 | |
|             String message = getRootCauseMsg(cause, true, null);
 | |
| 
 | |
|             throw new CustomModelException(errMsgId, new Object[] { message }, ex);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static String getRootCauseMsg(Throwable cause, boolean withAlfLogNum, String defaultMsg)
 | |
|     {
 | |
|         if (defaultMsg == null)
 | |
|         {
 | |
|             defaultMsg = "";
 | |
|         }
 | |
| 
 | |
|         String message = cause.getMessage();
 | |
|         if(message == null)
 | |
|         {
 | |
|             return defaultMsg;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return ((withAlfLogNum) ? message : message.replaceFirst("\\d+", "").trim());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public NodeRef createDownloadNode(final String modelFileName, boolean withAssociatedForm)
 | |
|     {
 | |
|         List<NodeRef> nodesToBeDownloaded = new ArrayList<>(2);
 | |
| 
 | |
|         NodeRef customModelNodeRef = getModelNodeRef(modelFileName);
 | |
|         if(customModelNodeRef == null)
 | |
|         {
 | |
|             throw new CustomModelException.ModelDoesNotExistException(MSG_MODEL_NOT_EXISTS, new Object[] { modelFileName });
 | |
|         }
 | |
|         // We create a copy of the model, so we can rename it, change its
 | |
|         // content type and move it to the download container in order to be
 | |
|         // cleaned by the download service cleanup job.
 | |
|         customModelNodeRef = createCustomModelCopy(modelFileName + ".xml", customModelNodeRef);
 | |
|         nodesToBeDownloaded.add(customModelNodeRef);
 | |
| 
 | |
|         if (withAssociatedForm)
 | |
|         {
 | |
|             NodeRef shareExtModuleNodeRef = null;
 | |
|             try
 | |
|             {
 | |
|                 shareExtModuleNodeRef = createCustomModelShareExtModuleRef(modelFileName);
 | |
|                 nodesToBeDownloaded.add(shareExtModuleNodeRef);
 | |
| 
 | |
|                 if (logger.isDebugEnabled())
 | |
|                 {
 | |
|                     StringBuilder msg = new StringBuilder();
 | |
|                     msg.append("Temp nodes created for download: Custom model nodeRef [")
 | |
|                                 .append(customModelNodeRef)
 | |
|                                 .append("] and its associated Share form nodeRef [")
 | |
|                                 .append(shareExtModuleNodeRef).append(']');
 | |
|                     logger.debug(msg.toString());
 | |
|                 }
 | |
|             }
 | |
|             catch (CustomModelException ex)
 | |
|             {
 | |
|                 // We don't throw the exception as the Model might be a
 | |
|                 // draft model and might have never been activated or never had any forms created for it.
 | |
|                 // So in this case we just construct the zip containing only the model.
 | |
|                 StringBuilder msg = new StringBuilder();
 | |
|                 msg.append("Constructing CMM zip file containing only the model [")
 | |
|                             .append(modelFileName)
 | |
|                             .append(".xml] without its associated share extension module, because: ")
 | |
|                             .append(ex.getMessage());
 | |
| 
 | |
|                 logger.warn(msg.toString());
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             if (logger.isDebugEnabled())
 | |
|             {
 | |
|                 StringBuilder msg = new StringBuilder();
 | |
|                 msg.append("Temp node created for download: Custom model nodeRef [")
 | |
|                             .append(customModelNodeRef).append(']');
 | |
|                 logger.debug(msg.toString());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         try
 | |
|         {
 | |
|             NodeRef archiveNodeRef = downloadService.createDownload(nodesToBeDownloaded.toArray(new NodeRef[nodesToBeDownloaded.size()]), false);
 | |
| 
 | |
|             if (logger.isDebugEnabled())
 | |
|             {
 | |
|                 StringBuilder msg = new StringBuilder();
 | |
|                 msg.append("Created download nodeRef [").append(archiveNodeRef).append(']');
 | |
|                 logger.debug(msg.toString());
 | |
|             }
 | |
| 
 | |
|             return archiveNodeRef;
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.create_err", ex);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Finds the {@code module} element within the Share persisted-extension
 | |
|      * XML file and then writes the XML fragment as the content of a newly created node.
 | |
|      *
 | |
|      * @param modelName the model name
 | |
|      * @return the created nodeRef
 | |
|      */
 | |
|     protected NodeRef createCustomModelShareExtModuleRef(final String modelName)
 | |
|     {
 | |
|         final String moduleId = "CMM_" + modelName;
 | |
| 
 | |
|         final NodeRef formNodeRef = getShareExtModule();
 | |
|         ContentReader reader = contentService.getReader(formNodeRef, ContentModel.PROP_CONTENT);
 | |
| 
 | |
|         if (reader == null)
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.share_ext_node_read_err");
 | |
|         }
 | |
| 
 | |
|         InputStream in = reader.getContentInputStream();
 | |
|         Node moduleIdXmlNode = null;
 | |
|         try
 | |
|         {
 | |
|             Document document = XMLUtil.parse(in); // the stream will be closed
 | |
| 
 | |
|             final String xpathQuery = "/extension//modules//module//id[.= '" + moduleId + "']";
 | |
| 
 | |
|             XPath xPath = XPathFactory.newInstance().newXPath();
 | |
|             XPathExpression expression = xPath.compile(xpathQuery);
 | |
| 
 | |
|             moduleIdXmlNode = (Node) expression.evaluate(document, XPathConstants.NODE);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.share_ext_file_parse_err", ex);
 | |
|         }
 | |
| 
 | |
|         if (moduleIdXmlNode == null)
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.share_ext_module_not_found", new Object[] { moduleId });
 | |
|         }
 | |
| 
 | |
|         final File moduleFile = TempFileProvider.createTempFile(moduleId, ".xml");
 | |
|         try
 | |
|         {
 | |
|             XMLUtil.print(moduleIdXmlNode.getParentNode(), moduleFile);
 | |
|         }
 | |
|         catch (IOException error)
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.share_ext_write_err", new Object[] { moduleId }, error);
 | |
|         }
 | |
| 
 | |
|         return doInTransaction(MSG_DOWNLOAD_CREATE_SHARE_EXT_ERR, true, new RetryingTransactionCallback<NodeRef>()
 | |
|         {
 | |
|             @Override
 | |
|             public NodeRef execute() throws Exception
 | |
|             {
 | |
|                 final NodeRef nodeRef = createDownloadTypeNode(moduleId + SHARE_EXT_MODULE_SUFFIX);
 | |
|                 ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
 | |
|                 writer.setMimetype(MimetypeMap.MIMETYPE_XML);
 | |
|                 writer.setEncoding("UTF-8");
 | |
|                 writer.putContent(moduleFile);
 | |
| 
 | |
|                 return nodeRef;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets Share persisted-extension nodeRef
 | |
|      */
 | |
|     protected NodeRef getShareExtModule()
 | |
|     {
 | |
|         List<NodeRef> results = searchService.selectNodes(getRootNode(), this.shareExtModulePath, null, this.namespaceDAO, false,
 | |
|                     SearchService.LANGUAGE_XPATH);
 | |
| 
 | |
|         if (results.isEmpty())
 | |
|         {
 | |
|             throw new CustomModelException("cmm.service.download.share_ext_file_not_found");
 | |
|         }
 | |
| 
 | |
|         return results.get(0);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates a copy of the custom model where the created node will be a child
 | |
|      * of download container.
 | |
|      *
 | |
|      * @param newName the model new name
 | |
|      * @param modelNodeRef existing model nodeRef
 | |
|      * @return the created nodeRef
 | |
|      */
 | |
|     protected NodeRef createCustomModelCopy(final String newName, final NodeRef modelNodeRef)
 | |
|     {
 | |
|         return doInTransaction(MSG_DOWNLOAD_COPY_MODEL_ERR, true, new RetryingTransactionCallback<NodeRef>()
 | |
|         {
 | |
|             @Override
 | |
|             public NodeRef execute() throws Exception
 | |
|             {
 | |
|                 final NodeRef newNodeRef = createDownloadTypeNode(newName);
 | |
|                 Serializable content = nodeService.getProperty(modelNodeRef, ContentModel.PROP_CONTENT);
 | |
|                 nodeService.setProperty(newNodeRef, ContentModel.PROP_CONTENT, content);
 | |
| 
 | |
|                return newNodeRef;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates node with a type {@link DownloadModel#TYPE_DOWNLOAD} within the
 | |
|      * download container (see
 | |
|      * {@link DownloadStorage#getOrCreateDowloadContainer()} )
 | |
|      * <p>
 | |
|      * Also, the {@code IndexControlAspect} is applied to the created node.
 | |
|      *
 | |
|      * @param name the node name
 | |
|      * @return the created nodeRef
 | |
|      */
 | |
|     private NodeRef createDownloadTypeNode(final String name)
 | |
|     {
 | |
|         final NodeRef newNodeRef = nodeService.createNode(
 | |
|                     downloadStorage.getOrCreateDowloadContainer(),
 | |
|                     ContentModel.ASSOC_CHILDREN,
 | |
|                     ContentModel.ASSOC_CHILDREN,
 | |
|                     DownloadModel.TYPE_DOWNLOAD,
 | |
|                     Collections.<QName, Serializable> singletonMap(ContentModel.PROP_NAME, name)).getChildRef();
 | |
| 
 | |
|         Map<QName, Serializable> aspectProperties = new HashMap<>(2);
 | |
|         aspectProperties.put(ContentModel.PROP_IS_INDEXED, Boolean.FALSE);
 | |
|         aspectProperties.put(ContentModel.PROP_IS_CONTENT_INDEXED, Boolean.FALSE);
 | |
|         nodeService.addAspect(newNodeRef, ContentModel.ASPECT_INDEX_CONTROL, aspectProperties);
 | |
| 
 | |
|         return newNodeRef;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public CustomModelsInfo getCustomModelsInfo()
 | |
|     {
 | |
|         List<CustomModelDefinition> page = getCustomModels(new PagingRequest(0, Integer.MAX_VALUE)).getPage();
 | |
| 
 | |
|         int activeModels = 0;
 | |
|         int activeTypes = 0;
 | |
|         int activeAspects = 0;
 | |
|         for (CustomModelDefinition cm : page)
 | |
|         {
 | |
|             if (cm.isActive())
 | |
|             {
 | |
|                 activeModels++;
 | |
|                 activeTypes += cm.getTypeDefinitions().size();
 | |
|                 activeAspects += cm.getAspectDefinitions().size();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         CustomModelsInfo info = new CustomModelsInfo();
 | |
|         info.setNumberOfActiveModels(activeModels);
 | |
|         info.setNumberOfActiveTypes(activeTypes);
 | |
|         info.setNumberOfActiveAspects(activeAspects);
 | |
|         return info;
 | |
|     }
 | |
| }
 |