/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* 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/>.
* #L%
*/
package org.alfresco.module.org_alfresco_module_rm.email;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.metadata.RFC822MetadataExtracter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
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.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
* Custom Email Mapping Service
*/
public class CustomEmailMappingServiceImpl extends AbstractLifecycleBean implements CustomEmailMappingService
{
/** Logger */
private static Log logger = LogFactory.getLog(CustomEmailMappingServiceImpl.class);
/** Node reference's to configuration elements */
private static final NodeRef CONFIG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "rm_emailmapping_config");
private static final NodeRef CONFIG_FOLDER_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "rm_config_folder");
/** Config file name */
private static final String CONFIG_NAME = "imapConfig.json";
/** Default custom mappings (TODO move to spring config) */
private static final CustomMapping[] DEFAULT_MAPPINGS =
{
new CustomMapping("Date", "dod:dateReceived"),
new CustomMapping("messageTo", "dod:address"),
new CustomMapping("messageFrom", "dod:originator"),
new CustomMapping("messageSent", "dod:publicationDate"),
new CustomMapping("messageCc", "dod:otherAddress")
};
/** Extractor */
private RFC822MetadataExtracter extracter;
/** Services */
private NodeService nodeService;
private NamespacePrefixResolver nspr;
private ContentService contentService;
private TransactionService transactionService;
/** Transient set of custom mappings */
private Set<CustomMapping> customMappings;
/** List of email mapping keys */
private List<String> emailMappingKeys;
/**
* @param nspr namespace service
*/
public void setNamespacePrefixResolver(NamespacePrefixResolver nspr)
{
this.nspr = nspr;
}
/**
* @param extractor extractor component
*/
public void setExtracter(RFC822MetadataExtracter extractor)
{
this.extracter = extractor;
}
/**
* @param nodeService node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param contentService content service
*/
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
/**
* @param transactionService transaction service
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/**
* @param emailMappingKeys email mapping keys
*/
public void setEmailMappingKeys(List<String> emailMappingKeys)
{
this.emailMappingKeys = emailMappingKeys;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.email.CustomEmailMappingService#getCustomMappings()
*/
public Set<CustomMapping> getCustomMappings()
{
if (customMappings == null)
{
// if we have a config file
if (nodeService.exists(CONFIG_NODE_REF))
{
// load the contents of the config file
customMappings = loadConfig();
}
else
{
customMappings = new HashSet<CustomMapping>();
// load the contents of the extractors property file
Map<String, Set<QName>> currentMapping = extracter.getCurrentMapping();
for (Map.Entry<String, Set<QName>> entry : currentMapping.entrySet())
{
Set<QName> set = entry.getValue();
for (QName qname : set)
{
CustomMapping value = new CustomMapping();
value.setFrom(entry.getKey());
QName resolvedQname = qname.getPrefixedQName(nspr);
value.setTo(resolvedQname.toPrefixString());
customMappings.add(value);
}
}
// if we have an old config file
NodeRef oldConfigNode = getOldConfigNode();
if (oldConfigNode != null)
{
// load the contents of the old config file
Set<CustomMapping> oldMappings = readOldConfig(oldConfigNode);
customMappings.addAll(oldMappings);
}
// load the hard coded mappings
for(CustomMapping mapping : DEFAULT_MAPPINGS)
{
customMappings.add(mapping);
}
// create the config file
saveConfig(customMappings);
}
}
return customMappings;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.email.CustomEmailMappingService#addCustomMapping(java.lang.String, java.lang.String)
*/
public void addCustomMapping(String from, String to)
{
if (StringUtils.isBlank(from) || StringUtils.isBlank(to))
{
throw new AlfrescoRuntimeException("Invalid values for from/to.");
}
// create custom mapping
CustomMapping customMapping = new CustomMapping(from, to);
// check whether we already have this mapping or not
Set<CustomMapping> customMappings = getCustomMappings();
if (customMappings.contains(customMapping))
{
throw new AlfrescoRuntimeException("Can not add custom email mapping, because duplicate mapping already exists.");
}
// else add the custom mapping (since we have already called getCustomMapping we can be sure
// the member variable is populated)
customMappings.add(customMapping);
// save the changes into the config file
saveConfig(customMappings);
// update the extractors configuration
updateExtractor();
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.email.CustomEmailMappingService#deleteCustomMapping(java.lang.String, java.lang.String)
*/
public void deleteCustomMapping(String from, String to)
{
if (StringUtils.isBlank(from) || StringUtils.isBlank(to))
{
throw new AlfrescoRuntimeException("Invalid values for from/to.");
}
// create custom mapping
CustomMapping customMapping = new CustomMapping(from, to);
// check whether we already have this mapping or not
Set<CustomMapping> customMappings = getCustomMappings();
if (customMappings.contains(customMapping))
{
// else remove the custom mapping (since we have already called getCustomMapping we can be sure
// the member variable is populated)
customMappings.remove(customMapping);
// save the changes into the config file
saveConfig(customMappings);
// update the extractors configuration
updateExtractor();
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.email.CustomEmailMappingService#getEmailMappingKeys()
*/
@Override
public List<String> getEmailMappingKeys()
{
return emailMappingKeys;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.email.EmailMappingKeyService#registerEMailMappingKey(java.lang.String)
*/
@Override
public void registerEMailMappingKey(String emailMappingKey)
{
ParameterCheck.mandatoryString("emailMappingKey", emailMappingKey);
emailMappingKeys.add(emailMappingKey);
}
/**
* Updates the extractor with the custom configuration.
*/
private void updateExtractor()
{
// convert the mapping information into the form understood by the extractor
Map<String, Set<QName>> newMapping = new HashMap<String, Set<QName>>(17);
for(CustomMapping mapping : getCustomMappings())
{
QName newQName = QName.createQName(mapping.getTo(), nspr);
Set<QName> values = newMapping.get(mapping.getFrom());
if(values == null)
{
values = new HashSet<QName>();
newMapping.put(mapping.getFrom(), values);
}
values.add(newQName);
}
// update the metadata extracter
extracter.setMapping(newMapping);
}
/**
* Loads the custom mappings from the configuration file.
*
* @return
*/
private Set<CustomMapping> loadConfig()
{
Set<CustomMapping> result = new HashSet<CustomMapping>();
ContentReader cr = contentService.getReader(CONFIG_NODE_REF, ContentModel.PROP_CONTENT);
if (cr != null)
{
String text = cr.getContentString();
try
{
JSONArray jsonArray = new JSONArray(new JSONTokener(text));
for(int i = 0 ; i < jsonArray.length(); i++)
{
JSONObject obj = jsonArray.getJSONObject(i);
CustomMapping mapping = new CustomMapping();
mapping.setFrom(obj.getString("from"));
mapping.setTo(obj.getString("to"));
result.add(mapping);
}
}
catch (JSONException je)
{
throw new AlfrescoRuntimeException("Unable to read custom email configuration", je);
}
}
return result;
}
/**
*
* @param customMappingsToSave
*/
private void saveConfig(Set<CustomMapping> customMappingsToSave)
{
if (!nodeService.exists(CONFIG_NODE_REF))
{
// create the config node
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(2);
properties.put(ContentModel.PROP_NAME, CONFIG_NAME);
properties.put(ContentModel.PROP_NODE_UUID, CONFIG_NODE_REF.getId());
nodeService.createNode(
CONFIG_FOLDER_NODE_REF,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, CONFIG_NAME),
ContentModel.TYPE_CONTENT,
properties);
}
// build JSON array of mappings
JSONArray jsonMappings = new JSONArray();
try
{
for(CustomMapping mapping : customMappingsToSave)
{
JSONObject obj = new JSONObject();
obj.put("from", mapping.getFrom());
obj.put("to", mapping.getTo());
jsonMappings.put(obj);
}
}
catch (JSONException je)
{
throw new AlfrescoRuntimeException("Unable to create JSON email mapping configuration during save.", je);
}
// update the content
ContentWriter writer = this.contentService.getWriter(CONFIG_NODE_REF, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding("UTF-8");
writer.putContent(jsonMappings.toString());
}
/**
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onBootstrap(ApplicationEvent event)
{
// run as System on bootstrap
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute()
{
try
{
// update the extractor with the custom mappings
updateExtractor();
}
catch (RuntimeException e)
{
// log a warning
if (logger.isWarnEnabled())
{
logger.warn(e.getMessage());
}
// reset the mappings
customMappings = null;
// rethrow
throw e;
}
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(callback);
return null;
}
}, AuthenticationUtil.getSystemUserName());
}
/**
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onShutdown(ApplicationEvent arg0)
{
// No implementation
}
/**
* Helper method to get the old configuration node. This is used during the migration
* from 1.0 to 2.0.
* <p>
* Returns null if it does not exist.
*
* @return {@link NodeRef} node reference of the old configuration node, null otherwise
*/
private NodeRef getOldConfigNode()
{
NodeRef rootNode = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
return nodeService.getChildByName(rootNode, RecordsManagementModel.ASSOC_EMAIL_CONFIG, CONFIG_NAME);
}
/**
* Reads the old configuration node. This is used during the migration from 1.0 to 2.0.
*
* @param nodeRef the old configuration node reference
* @return {@link Set}<{@link CustomMapping}> set of the custom mappings stored in the old configuration
*/
private Set<CustomMapping> readOldConfig(NodeRef nodeRef)
{
Set<CustomMapping> newMappings = new HashSet<CustomMapping>();
ContentReader cr = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (cr != null)
{
String text = cr.getContentString();
try
{
JSONArray jsonArray = new JSONArray(new JSONTokener(text));
for(int i = 0 ; i < jsonArray.length(); i++)
{
JSONObject obj = jsonArray.getJSONObject(i);
CustomMapping mapping = new CustomMapping();
mapping.setFrom(obj.getString("from"));
mapping.setTo(obj.getString("to"));
newMappings.add(mapping);
}
return newMappings;
}
catch (JSONException je)
{
logger.warn("unable to read custom email configuration", je);
return newMappings;
}
}
return newMappings;
}
}