/*
* Copyright (C) 2005-2010 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 .
*/
package org.alfresco.wcm.sandbox.script;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.dictionary.DictionaryNamespaceComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
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.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.wcm.asset.AssetInfo;
import org.alfresco.wcm.asset.AssetService;
import org.alfresco.wcm.sandbox.SandboxService;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.surf.util.ISO8601DateFormat;
/**
* WCM Asset in a sandbox exposed over Java Script API.
* @author mrogers
*
*/
public class Asset implements Serializable
{
private static final QName NAMESPACE_SERVICE = QName.createQName("", "namespaceService");
/**
*
*/
private static final long serialVersionUID = -5759260478423750966L;
private AssetInfo asset;
private Sandbox sandbox;
private Map props;
private Set updatedProperties = new HashSet();
public Asset(Sandbox sandbox, AssetInfo asset)
{
this.sandbox = sandbox;
this.asset = asset;
}
/**
* The creator of this asset
* @return the creator
*/
public String getCreator()
{
return asset.getCreator();
}
public Date getCreatedDate()
{
return asset.getCreatedDate();
}
public long getFileSize()
{
return asset.getFileSize();
}
public String getCreatedDateAsISO8601()
{
return ISO8601DateFormat.format(getCreatedDate());
}
public String getModifier()
{
return asset.getModifier();
}
public Date getModifiedDate()
{
return asset.getModifiedDate();
}
public String getModifiedDateAsISO8601()
{
return ISO8601DateFormat.format(getModifiedDate());
}
/**
* rename this asset
* @param newName
*/
public Asset rename(String newName)
{
if(!newName.equals(asset.getName()))
{
AssetInfo newAsset = getAssetService().renameAsset(asset, newName);
this.asset = newAsset;
}
return this;
}
/**
* move this asset
* @param newPath
*/
public Asset move(String newPath)
{
if(!newPath.equals(asset.getPath()))
{
AssetInfo newAsset = getAssetService().moveAsset(asset, newPath);
this.asset = newAsset;
}
return this;
}
public String getName()
{
return asset.getName();
}
/**
* Get the full path of this asset eg. /www/avm_webapps/ROOT/myFile.jpg
* @return the path of this asset.
*/
public String getPath()
{
return asset.getPath();
}
public boolean isFile()
{
return asset.isFile();
}
public boolean isFolder()
{
return asset.isFolder();
}
public boolean isDeleted()
{
return asset.isDeleted();
}
public boolean isLocked()
{
return asset.isLocked();
}
public String lockOwner()
{
return asset.getLockOwner();
}
public int getVersion()
{
return asset.getSandboxVersion();
}
/**
* Get the properties as a key value pair. The key will be either a local qname e.g. "cm:content" or
* a global qname e.g. "{http://www.alfresco.com/content/1.0}content".
*
* Some properties will be updatable, protected properties are not.
*
* @return the properties in a key, value pair
*/
public Map getProperties()
{
if(props == null) {
// Note there is something very strange going on with scope which is why there's this guff with propsX
Map propsX = new HashMap();
props = propsX;
NamespaceService ns = getNamespaceService();
if(!asset.isDeleted())
{
Map intprops = getAssetService().getAssetProperties(asset);
for (QName qname : intprops.keySet())
{
QName prefixQname = qname.getPrefixedQName(ns);
Serializable propValue = intprops.get(qname);
try
{
propsX.put(prefixQname.toPrefixString(), (null == propValue) ? (null):(propValue.toString()));
}
catch (NamespaceException ne)
{ // No local name, only thing I can do is use the full namke
propsX.put(qname.toString(), propValue.toString());
}
}
}
}
return props;
}
/**
* Save the properties please note some system properties are protected and cannot be updated. If you attempt to update a protected property your request will be ignored.
*
* @param properties
*/
public void save()
{
if (!updatedProperties.isEmpty() && (null != props))
{
Map newProps = new HashMap(props.size());
QName type = getAssetType();
DictionaryService dictionaryService = getDictionaryService();
TypeDefinition typeDefinition = (null != type) ? (dictionaryService.getType(type)) : (null);
if (null != typeDefinition)
{
if (updatedProperties.contains(ContentModel.PROP_NAME))
{
updatedProperties.remove(ContentModel.PROP_NAME);
rename(getPropertyValue(ContentModel.PROP_NAME));
}
Map propertyDefinitions = typeDefinition.getProperties();
for (QName key : updatedProperties)
{
PropertyDefinition propertyDefinition = (propertyDefinitions.containsKey(key)) ? (propertyDefinitions.get(key)) : (dictionaryService.getProperty(key));
Serializable value = convertValueToDataType(key, propertyDefinition.getDataType().getName(), getPropertyValue(key));
newProps.put(key, value);
}
getAssetService().setAssetProperties(asset, newProps);
updatedProperties.clear();
}
else
{
throw new AVMNotFoundException("The type property of the current Asset not found");
}
}
}
private String getPropertyValue(QName key)
{
String prefixedQName = completeContentModelQName(key).toPrefixString();
return props.containsKey(prefixedQName) ? (props.get(prefixedQName)) : (props.get(key.toString()));
}
private Serializable convertValueToDataType(QName propertyName, QName dataType, String textualValue)
{
Serializable result = null;
if (null != textualValue)
{
try
{
if (DataTypeDefinition.BOOLEAN.equals(dataType))
{
result = Boolean.parseBoolean(textualValue);
}
else if (DataTypeDefinition.DOUBLE.equals(dataType))
{
result = Double.parseDouble(textualValue);
}
else if (DataTypeDefinition.FLOAT.equals(dataType))
{
result = Float.parseFloat(textualValue);
}
else if (DataTypeDefinition.INT.equals(dataType))
{
result = Integer.parseInt(textualValue);
}
else if (DataTypeDefinition.LONG.equals(dataType))
{
result = Long.parseLong(textualValue);
}
else if (DataTypeDefinition.NODE_REF.equals(dataType))
{
result = (NodeRef.isNodeRef(textualValue)) ? (new NodeRef(textualValue)) : (null);
}
else if (DataTypeDefinition.QNAME.equals(dataType))
{
result = QName.resolveToQName(getNamespaceService(), textualValue);
}
else if (DataTypeDefinition.CONTENT.equals(dataType))
{
result = ContentData.createContentProperty(textualValue);
}
else if (DataTypeDefinition.TEXT.equals(dataType) || DataTypeDefinition.MLTEXT.equals(dataType))
{
result = textualValue;
}
}
catch (NumberFormatException e)
{
throw new AVMBadArgumentException("Value for the '" + propertyName + "' property is invalid! Conversion error: " + e.toString());
}
}
// TODO: Conversion for other DataTypes
return result;
}
/**
* @param properties
* @throws JSONException
*/
public void setProperties(Object nativeProperties) throws JSONException
{
JSONObject properties = (JSONObject) nativeProperties;
if ((null != asset) && !asset.isDeleted())
{
Map currentProperties = getProperties();
if (null == currentProperties)
{
throw new AVMNotFoundException("No a property found for the current Asset");
}
QName type = getAssetType();
DictionaryService dictionaryService = getDictionaryService();
TypeDefinition typeDefinition = (null != type) ? (dictionaryService.getType(type)) : (null);
if (null != typeDefinition)
{
Map propertyDefinitions = typeDefinition.getProperties();
for (String key : JSONObject.getNames(properties))
{
QName qName = QName.resolveToQName(getNamespaceService(), key);
if (ContentModel.PROP_CONTENT.equals(qName))
{
updatedProperties.clear();
throw new AVMBadArgumentException("The 'Content' property can't be set with the 'setProperties()' method! Use a 'writeContent()' instead");
}
PropertyDefinition property = (propertyDefinitions.containsKey(qName)) ? (propertyDefinitions.get(qName)) : (dictionaryService.getProperty(qName));
if (null != property)
{
// TODO: Maybe are multi-valued properties operable?
if (property.isProtected() || property.isMultiValued())
{
updatedProperties.clear();
throw new AVMBadArgumentException("The '" + key + "' property is not updatable");
}
Object associatedValue = properties.get(key);
qName = completeContentModelQName(qName);
currentProperties.put(qName.toPrefixString(), (null != associatedValue) ? (associatedValue.toString()) : (null));
updatedProperties.add(property.getName());
}
else
{
updatedProperties.clear();
throw new AVMNotFoundException("The '" + key + "' property definition can't be found");
}
}
props = currentProperties;
}
}
}
private QName completeContentModelQName(QName qName)
{
if (qName.getLocalName().equals(qName.getPrefixString()) && NamespaceService.CONTENT_MODEL_1_0_URI.equals(qName.getNamespaceURI()))
{
DictionaryNamespaceComponent service = (DictionaryNamespaceComponent) getSandbox().getWebproject().getWebProjects().getServiceRegistry().getService(NAMESPACE_SERVICE);
return QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, qName.getLocalName(), service);
}
return qName;
}
private QName getAssetType()
{
final NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath());
final NodeService nodeService = getNodeService();
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
{
public QName doWork() throws Exception
{
RetryingTransactionHelper helper = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getRetryingTransactionHelper();
return helper.doInTransaction(new RetryingTransactionCallback()
{
public QName execute() throws Throwable
{
return nodeService.getType(assetNodeRef);
}
});
}
}, AuthenticationUtil.getFullyAuthenticatedUser());
}
/**
* Updates a content of a current Asset
*
* @param content {@link String} value which represents new textual content
* @return true
if a content has been set without errors
*/
public boolean writeContent(String content)
{
NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath());
ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService();
ContentWriter writer = contentService.getWriter(assetNodeRef, ContentModel.PROP_CONTENT, true);
if ((null != writer) && (null != content))
{
writer.setMimetype("text/plain");
writer.setEncoding("UTF-8");
writer.putContent(content);
return true;
}
return false;
}
/**
* Updates a content of the current Asset
*
* @param content a {@link Content} value which represents new content
* @return true
if a content has been set without errors
*/
public boolean writeContent(Content content)
{
NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath());
ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService();
ContentWriter writer = contentService.getWriter(assetNodeRef, ContentModel.PROP_CONTENT, true);
if ((null != writer) && (null != content))
{
writer.setMimetype(content.getMimetype());
writer.setEncoding(content.getEncoding());
writer.putContent(content.getInputStream());
return true;
}
return false;
}
/**
* Returns textual representation of the Asset content
*
* @return content as a text
* @throws ContentIOException
* @throws IOException
*/
public String getContent() throws ContentIOException, IOException
{
NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath());
ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService();
ContentReader reader = contentService.getReader(assetNodeRef, ContentModel.PROP_CONTENT);
return (null != reader) ? (reader.getContentString()) : (null);
}
/**
* Submit this asset to staging
* @param submitLabel
* @param submitComment
*/
public void submit(String submitLabel, String submitComment)
{
List items = new ArrayList(1);
items.add(this.getPath());
getSandboxService().submitList(getSandbox().getSandboxRef(), items, submitLabel, submitComment);
}
/**
* Delete this asset, after it has been deleted do not use this asset.
*/
public void deleteAsset()
{
getAssetService().deleteAsset(this.asset);
}
/**
* revert this asset
*/
public void revert()
{
List items = new ArrayList(1);
items.add(this.getPath());
getSandboxService().revertList(getSandbox().getSandboxRef(), items);
}
/**
* Get children of this asset, returns an empty array if there are no children.
* Only folders have children.
*/
public Asset[] getChildren()
{
Asset[] ret = new Asset[0];
if(asset.isFolder())
{
int i = 0;
List assets = getAssetService().listAssets(getSandbox().getSandboxRef(), asset.getPath(), true);
ret = new Asset[assets.size()];
for(AssetInfo asset : assets)
{
ret[i++]=new Asset(sandbox, asset);
}
}
return ret;
}
/**
* create a new file with the specified properties and content.
* @param name the name of the file
* @param stringContent the content of the file. Can be null.
*/
public void createFile(String name, String stringContent)
{
ContentWriter writer = getAssetService().createFile(getSandbox().getSandboxRef(), asset.getPath(), name, null);
if(stringContent != null)
{
writer.putContent(stringContent);
}
}
/**
* create a new folder
* @param name the name of the new folder
*/
public void createFolder(String name)
{
getAssetService().createFolder(getSandbox().getSandboxRef(), asset.getPath(), name, null);
}
/**
* Get the parent sandbox which contains this asset
* @return the parent sandbox which contains this asset
*/
public Sandbox getSandbox()
{
return this.sandbox;
}
/**
* @return
*/
private SandboxService getSandboxService()
{
return getSandbox().getWebproject().getWebProjects().getSandboxService();
}
/**
* Get the asset service
* @return the asset service
*/
private AssetService getAssetService()
{
return getSandbox().getWebproject().getWebProjects().getAssetService();
}
/**
* Get the asset service
* @return the asset service
*/
private NamespaceService getNamespaceService()
{
return getSandbox().getWebproject().getWebProjects().getNamespaceService();
}
private NodeService getNodeService()
{
return getSandbox().getWebproject().getWebProjects().getServiceRegistry().getNodeService();
}
private DictionaryService getDictionaryService()
{
return getSandbox().getWebproject().getWebProjects().getServiceRegistry().getDictionaryService();
}
}