/*
* Copyright (C) 2003-2012 eXo Platform SAS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.exoplatform.ecm.connector.platform;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Session;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import org.artofsolving.jodconverter.office.OfficeException;
import org.exoplatform.services.cms.CmsService;
import org.exoplatform.services.cms.JcrInputProperty;
import org.exoplatform.services.cms.documents.TrashService;
import org.exoplatform.services.cms.jodconverter.JodConverterService;
import org.exoplatform.services.cms.taxonomy.TaxonomyService;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.distribution.DataDistributionManager;
import org.exoplatform.services.jcr.ext.distribution.DataDistributionMode;
import org.exoplatform.services.jcr.ext.distribution.DataDistributionType;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.publication.WCMPublicationService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
/**
* Created by The eXo Platform SAS
* Author : Nguyen Anh Vu
* vuna@exoplatform.com
* Jun 20, 2012
*/
@Path("/contents/populate/")
public class PopulateConnector implements ResourceContainer {
private static final Log LOG = ExoLogger.getLogger(PopulateConnector.class.getName());
/** The Constant LAST_MODIFIED_PROPERTY. */
protected static final String LAST_MODIFIED_PROPERTY = "Last-Modified";
/** The Constant IF_MODIFIED_SINCE_DATE_FORMAT. */
protected static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
protected static final String WORKSPACE_NAME = "collaboration";
/** Source folder */
private static final String SOURCE_DATA_FOLDER_PATH = "/contents";
/** Dictionary file */
private static final String DICTIONARY_FILE = "dictionary.txt";
/** Files to be imported */
private static final String[] SOURCE_FILES1 = {"content.doc application/msword", "content.pdf application/pdf",
"content.ppt application/ppt","content.xls application/xls"
};
private static final String[] SOURCE_FILES2 = {"image.jpg image/jpeg", "image.jpeg image/jpeg", "image.gif image/gif",
"image.png image/png"};
private static final String IMPORTED_DOCUMENTS_FOLDER = "importedDocuments";
private static final int MAX_NORMAL_DATA_RATE = 300;
private static final int DEFAULT_DOCUMENT_SIZE = 1;//1kb
private RepositoryService repoService_;
private CmsService cmsService_;
private WCMPublicationService publicationService_;
private DataDistributionManager dataDistributionManager_;
private TaxonomyService taxonomyService_;
private JodConverterService jodConverter_;
public PopulateConnector(RepositoryService repositoryService, CmsService cmsService, WCMPublicationService publicationService,
DataDistributionManager dataDistributionManager, TaxonomyService taxonomyService,
JodConverterService jodConverter) {
repoService_ = repositoryService;
cmsService_ = cmsService;
publicationService_ = publicationService;
dataDistributionManager_ = dataDistributionManager;
taxonomyService_ = taxonomyService;
jodConverter_ = jodConverter;
}
/**
* Initializes the data to use later
* @param isPublishDoc indicates if the newly created documents are published.
* @param isGenerateNewData indicates if data is generated new, not copy
* @param size size of newly generated data
* @return
*/
private Response initializeLoadData(boolean isPublishDoc, boolean isGenerateNewData, int size) {
SessionProvider sessionProvider = null;
try {
sessionProvider = WCMCoreUtils.getUserSessionProvider();
Session session = sessionProvider.getSession(WORKSPACE_NAME, repoService_.getCurrentRepository());
//remove importedFolderNode
if (session.getRootNode().hasNode(IMPORTED_DOCUMENTS_FOLDER)) {
WCMCoreUtils.getService(TrashService.class).moveToTrash(session.getRootNode().getNode(IMPORTED_DOCUMENTS_FOLDER),
sessionProvider);
session.save();
}
//create importedFolderNode
Node importedFolderNode = session.getRootNode().addNode(IMPORTED_DOCUMENTS_FOLDER);
importedFolderNode.addMixin(NodetypeConstant.EXO_HIDDENABLE);
session.save();
List<String> generatedFiles = new ArrayList<String>(Arrays.asList(SOURCE_FILES1));
List<String> initializedFiles = new ArrayList<String>(Arrays.asList(SOURCE_FILES2));
if (!isGenerateNewData) {
generatedFiles.clear();
initializedFiles.addAll(Arrays.asList(SOURCE_FILES1));
}
//import source files into JCR
for (String importedFile : initializedFiles) {
String importedFileName = importedFile.split(" ")[0];
String mimeType = importedFile.split(" ")[1];
if (!session.itemExists(importedFolderNode.getPath() + "/" + importedFileName)) {
InputStream inputStream = this.getClass().getResourceAsStream(SOURCE_DATA_FOLDER_PATH + "/" + importedFileName);
String fileNodeName = cmsService_.storeNode("nt:file", importedFolderNode,
getInputProperties(importedFileName, inputStream, mimeType), true);
if (isPublishDoc) {
publicationService_.updateLifecyleOnChangeContent((Node)session.getItem(fileNodeName), "acme", "root","published");
}
}
}
//generate source file
for (String importedFile : generatedFiles) {
String importedFileName = importedFile.split(" ")[0];
String mimeType = importedFile.split(" ")[1];
if (!session.itemExists(importedFolderNode.getPath() + "/" + importedFileName)) {
String fileNodeName = generateFile(importedFolderNode, importedFileName, size, mimeType);
if (isPublishDoc) {
publicationService_.updateLifecyleOnChangeContent((Node)session.getItem(fileNodeName), "acme", "root","published");
}
}
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(e);
}
return Response.serverError().build();
}
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
}
/**
*
* @param parentNode
* @param fileName
* @param size
* @return
*/
private String generateFile(Node parentNode, String fileName, int size, String mimeType) throws Exception {
String fileExtension = fileName.substring(fileName.indexOf('.') + 1);
//build the set of word to generate document
BufferedReader br = null;
br = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream(SOURCE_DATA_FOLDER_PATH + "/" +
DICTIONARY_FILE)));
String line = null;
List<String> dictionary = new ArrayList<String>();
while ((line = br.readLine()) != null) {
for (String word : line.split("[ .,;]")) {
dictionary.add(word);
}
}
br.close();
//build the document content
size = size == 0 ? DEFAULT_DOCUMENT_SIZE : size;
StringBuilder content = new StringBuilder("Lorem ");
int sentenceLength = (int)((Math.random() * 20)) + 5;
while (content.length() < size * 1024) {
content.append(dictionary.get((int)(Math.random() * dictionary.size())));
if (--sentenceLength == 0) {
content.append(". ");
sentenceLength = (int)((Math.random() * 20)) + 5;
} else {
content.append(' ');
}
}
content.append('.');
File tempFile = null;
if(fileExtension.equalsIgnoreCase("doc")) {
//create a temporary txt file containing generated content at previous step
tempFile = File.createTempFile("content_temp", fileExtension);
InputStream input = new BufferedInputStream(new ByteArrayInputStream(content.toString().getBytes()));
OutputStream out = new BufferedOutputStream((new FileOutputStream(tempFile)));
// create temp file to store original data of nt:file node
File in = File.createTempFile("content_tmp", null);
read(input, new BufferedOutputStream(new FileOutputStream(in)));
try {
boolean success = jodConverter_.convert(in, tempFile, fileExtension);
// If the converting was failure then delete the content temporary file
if (!success) {
tempFile.delete();
}
} catch (OfficeException connection) {
tempFile.delete();
if (LOG.isErrorEnabled()) {
LOG.error("Exception when using Office Service");
}
} finally {
in.delete();
out.flush();
out.close();
}
} else {
try {
DocumentRenderer documentRender = new DocumentRenderer();
boolean success = documentRender.createDocument(content.toString(), fileName, fileExtension);
if(success) tempFile = new File(fileName);
} catch(Exception ex) {
if (LOG.isErrorEnabled()) {
LOG.error("Exception when creating document");
}
}
}
//import the newly created file into jcr
InputStream inputStream = new FileInputStream(tempFile);
String fileNodeName = cmsService_.storeNode("nt:file", parentNode,
getInputProperties(fileName, inputStream, mimeType), true);
return fileNodeName;
}
private void read(InputStream is, OutputStream os) throws Exception {
int bufferLength = 1024;
int readLength = 0;
while (readLength > -1) {
byte[] chunk = new byte[bufferLength];
readLength = is.read(chunk);
if (readLength > 0) {
os.write(chunk, 0, readLength);
}
}
os.flush();
os.close();
}
/**
* Initializes the data to use later
* @param isPublishDoc indicates if the newly created documents are published.
* @return
*/
@GET
@Path("/initialLoad")
public Response initialLoad(@QueryParam("isPublishDoc") boolean isPublishDoc) {
return initializeLoadData(isPublishDoc, false, 0);
}
/**
* Creates mass amount of data
* @param name the node name
* @param docType type of the document to create
* @param from document name will start with suffix "from", and increasing by one for the next doc
* @param to the bottom range of document name
* @param folderPath the location where all documents will be created
* @param categories the categories which created documents are attached.
* @return
*/
@GET
@Path("/storage")
public Response storage(@QueryParam("name") String name, @QueryParam("docType") String docType,
@QueryParam("from") int from, @QueryParam("to") int to,
@QueryParam("workspace") String workspace,
@QueryParam("folderPath") String folderPath,
@QueryParam("categories") String categories,
@QueryParam("size") Integer size) {
SessionProvider sessionProvider = null;
try {
//0.initial data
if (!folderPath.startsWith("/")) {
folderPath = "/" + folderPath;
}
sessionProvider = WCMCoreUtils.getUserSessionProvider();
Session sourceSession = sessionProvider.getSession(WORKSPACE_NAME, repoService_.getCurrentRepository());
Session session = sessionProvider.getSession(workspace, repoService_.getCurrentRepository());
initializeLoadData(true, true, (size == null ? 0 : size));
//1.get source node
Node sourceNode = getSourceNode(sourceSession, IMPORTED_DOCUMENTS_FOLDER, docType);
Node targetFolder = dataDistributionManager_.getDataDistributionType(DataDistributionMode.NONE).getOrCreateDataNode(
session.getRootNode(), folderPath);
//2.store nodes
if (to - from < MAX_NORMAL_DATA_RATE) {
//normal mode
for (int i = from; i <= to; i++) {
String storedNodePath = new StringBuilder(folderPath).append("/").append(name).
append(i).append('.').append(docType).toString();
if (!session.itemExists(storedNodePath)) {
session.getWorkspace().copy(WORKSPACE_NAME, sourceNode.getPath(), storedNodePath);
Node newNode = ((Node)session.getItem(storedNodePath));
newNode.setProperty("exo:title", name + i + '.' + docType);
session.save();
}
Node newNode = ((Node)session.getItem(storedNodePath));
addTaxonomy(newNode, categories);
}
} else {
//optimize storage mode
DataDistributionType dataDistributionType =
dataDistributionManager_.getDataDistributionType(DataDistributionMode.OPTIMIZED);
Node parentFolder = null;
for (int i = from; i <= to; i++) {
if ((i == from) || (i % 100 == 0)) {
parentFolder = dataDistributionType.getOrCreateDataNode(targetFolder, name + i);
}
String storedNodePath = new StringBuilder(parentFolder.getPath()).append("/").append(name).
append(i).append('.').append(docType).toString();
if (!session.itemExists(storedNodePath)) {
session.getWorkspace().copy(WORKSPACE_NAME, sourceNode.getPath(), storedNodePath);
Node newNode = ((Node)session.getItem(storedNodePath));
newNode.setProperty("exo:title", name + i + '.' + docType);
session.save();
}
Node newNode = ((Node)session.getItem(storedNodePath));
addTaxonomy(newNode, categories);
}
}
if (LOG.isInfoEnabled()) {
LOG.info("Data Injector for ECMS finished successfully!....");
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(e);
}
if (LOG.isInfoEnabled()) {
LOG.info("Data Injector for ECMS failed!....");
}
return Response.serverError().build();
}
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
}
/**
* returns the document node in the folderPath corresponding to the given docType
* @param session session in which node will be retrieved
* @param folderPath location of the nodes to search
* @param docType type of document to get
* @return the Node
* @throws Exception
*/
private Node getSourceNode(Session session, String folderPath, String docType) throws Exception {
Node folderNode = session.getRootNode().getNode(folderPath);
for (NodeIterator iter = folderNode.getNodes(); iter.hasNext();) {
Node childNode = iter.nextNode();
String nodeName = childNode.getName();
int index = nodeName.indexOf(".");
if (index > -1) {
if (docType != null && docType.equals(nodeName.substring(index+1))) {
return childNode;
}
}
}
return null;
}
/**
* adds taxonomies to the given node
* @param node the node to add taxonomy
* @param categoryList the categories which will be assigned to the node
* @throws Exception
*/
private void addTaxonomy(Node node, String categoryList) throws Exception {
if (categoryList == null) {
return;
}
for (String category : categoryList.split(",")) {
try {
List<String> arrayCategoryPath = new ArrayList<String>();
for (String categoryPart : category.split("/")) {
if (categoryPart.trim().length() > 0) {
arrayCategoryPath.add(categoryPart.trim());
}
}
if (arrayCategoryPath.size() == 1) {
taxonomyService_.addCategory(node, arrayCategoryPath.get(0), "");
} else {
StringBuffer categoryPath = new StringBuffer("/");
for (int i = 1; i < arrayCategoryPath.size(); i++) {
categoryPath.append(arrayCategoryPath.get(i)).append("/");
}
taxonomyService_.addCategory(node, arrayCategoryPath.get(0), categoryPath.toString());
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(e);
}
}
}
}
/**
* gets the input properties map by given parameters
* @param name the node name
* @param inputStream the input stream
* @param mimeType the mimetype
* @return
*/
private Map<String, JcrInputProperty> getInputProperties(String name,
InputStream inputStream, String mimeType) {
Map<String, JcrInputProperty> inputProperties = new HashMap<String, JcrInputProperty>();
JcrInputProperty nodeInput = new JcrInputProperty();
nodeInput.setJcrPath("/node");
nodeInput.setValue(name);
nodeInput.setMixintype("mix:i18n,mix:votable,mix:commentable");
nodeInput.setType(JcrInputProperty.NODE);
inputProperties.put("/node", nodeInput);
JcrInputProperty jcrContent = new JcrInputProperty();
jcrContent.setJcrPath("/node/jcr:content");
jcrContent.setValue("");
jcrContent.setMixintype("dc:elementSet");
jcrContent.setNodetype("nt:resource");
jcrContent.setType(JcrInputProperty.NODE);
inputProperties.put("/node/jcr:content", jcrContent);
JcrInputProperty jcrData = new JcrInputProperty();
jcrData.setJcrPath("/node/jcr:content/jcr:data");
jcrData.setValue(inputStream);
inputProperties.put("/node/jcr:content/jcr:data", jcrData);
JcrInputProperty jcrMimeType = new JcrInputProperty();
jcrMimeType.setJcrPath("/node/jcr:content/jcr:mimeType");
jcrMimeType.setValue(mimeType);
inputProperties.put("/node/jcr:content/jcr:mimeType", jcrMimeType);
JcrInputProperty jcrLastModified = new JcrInputProperty();
jcrLastModified.setJcrPath("/node/jcr:content/jcr:lastModified");
jcrLastModified.setValue(new GregorianCalendar());
inputProperties.put("/node/jcr:content/jcr:lastModified", jcrLastModified);
JcrInputProperty jcrEncoding = new JcrInputProperty();
jcrEncoding.setJcrPath("/node/jcr:content/jcr:encoding");
jcrEncoding.setValue("UTF-8");
inputProperties.put("/node/jcr:content/jcr:encoding", jcrEncoding);
return inputProperties;
}
}