Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
Merge pull request #73 from zanata/isolate-file-storage
Browse files Browse the repository at this point in the history
WIP: raw file migration from database to file system
  • Loading branch information
seanf committed Jul 26, 2013
2 parents 73b0323 + 3b83e89 commit 8ebb8fd
Show file tree
Hide file tree
Showing 16 changed files with 515 additions and 75 deletions.
12 changes: 2 additions & 10 deletions zanata-model/src/main/java/org/zanata/model/HRawDocument.java
Expand Up @@ -21,12 +21,10 @@
package org.zanata.model;

import java.io.Serializable;
import java.sql.Blob;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Lob;
import javax.persistence.OneToOne;

import lombok.Getter;
Expand All @@ -52,7 +50,7 @@ public class HRawDocument extends ModelEntityBase implements Serializable

// TODO none of these should allow null
private String contentHash;
private Blob content;
private String fileId;
private DocumentType type;
private String uploadedBy;

Expand All @@ -79,12 +77,6 @@ public String getContentHash()
return contentHash;
}

@Lob
public Blob getContent()
{
return content;
}

@Enumerated(EnumType.STRING)
public DocumentType getType()
{
Expand All @@ -105,6 +97,6 @@ public String toString()
+ ",contentHash=" + contentHash + "]";
}

// TODO override equals to use contentHash
// TODO override equals to use contentHash, type, parameters, etc.

}
Expand Up @@ -182,6 +182,11 @@ public String getServerPath()
return configuredValue;
}

public String getDocumentFileStorageLocation()
{
return jndiBackedConfig.getDocumentFileStorageLocation();
}

public String getDomainName()
{
return databaseBackedConfig.getDomain();
Expand Down
Expand Up @@ -124,7 +124,7 @@ public class ProjectIterationFilesAction implements Serializable
@In
private LocaleDAO localeDAO;

@In("blobPersistService")
@In("filePersistService")
private FilePersistService filePersistService;

@In
Expand Down Expand Up @@ -326,6 +326,7 @@ private void uploadPotFile()
}

// TODO add logging for disk writing errors
// TODO damason: unify this with Source/TranslationDocumentUpload
private void uploadAdapterFile()
{
String fileName = documentFileUpload.getFileName();
Expand Down
Expand Up @@ -54,6 +54,7 @@ public class JndiBackedConfig implements Serializable
private static final String KEY_AUTH_POLICY = "java:global/zanata/security/auth-policy-names/";
private static final String KEY_ADMIN_USERS = "java:global/zanata/security/admin-users";
private static final String KEY_DEFAULT_FROM_ADDRESS = "java:global/zanata/email/default-from-address";
private static final String KEY_DOCUMENT_FILE_STORE = "java:global/zanata/files/document-storage-directory";
private static final String KEY_EMAIL_HOST = "java:global/zanata/smtp/host";
private static final String KEY_EMAIL_PORT = "java:global/zanata/smtp/port";
private static final String KEY_EMAIL_USERNAME = "java:global/zanata/smtp/username";
Expand Down Expand Up @@ -160,6 +161,11 @@ public String getDefaultFromEmailAddress()
return getConfigValue(KEY_DEFAULT_FROM_ADDRESS);
}

public String getDocumentFileStorageLocation()
{
return getConfigValue(KEY_DOCUMENT_FILE_STORE);
}

public String getSmtpHostName()
{
return getConfigValue(KEY_EMAIL_HOST);
Expand Down
6 changes: 6 additions & 0 deletions zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java
Expand Up @@ -21,6 +21,7 @@
import org.zanata.common.LocaleId;
import org.zanata.common.TransUnitCount;
import org.zanata.common.TransUnitWords;
import org.zanata.file.GlobalDocumentId;
import org.zanata.model.HDocument;
import org.zanata.model.HLocale;
import org.zanata.model.HProjectIteration;
Expand Down Expand Up @@ -325,6 +326,11 @@ public Map<LocaleId, ContainerTranslationStatistics> getStatistics(long docId, L
return returnStats;
}

public HDocument getByGlobalId(GlobalDocumentId id)
{
return getByProjectIterationAndDocId(id.getProjectSlug(), id.getVersionSlug(), id.getDocId());
}

public HDocument getByProjectIterationAndDocId(final String projectSlug, final String iterationSlug, final String docId)
{
// TODO caching might be better with getByDocIdAndIteration(ProjectIterationDAO.getBySlug(), docId)
Expand Down
59 changes: 2 additions & 57 deletions zanata-war/src/main/java/org/zanata/file/BlobPersistService.java
Expand Up @@ -20,41 +20,26 @@
*/
package org.zanata.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.SQLException;

import javax.ws.rs.core.Response.Status;

import lombok.extern.slf4j.Slf4j;

import org.hibernate.LobHelper;
import org.hibernate.Session;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.zanata.dao.DocumentDAO;
import org.zanata.exception.ChunkUploadException;
import org.zanata.model.HDocument;
import org.zanata.model.HDocumentUploadPart;
import org.zanata.model.HRawDocument;

// TODO damason: replace with file system implementation, named something like "filePartPersistService"
@Name("blobPersistService")
@Scope(ScopeType.STATELESS)
@AutoCreate
@Slf4j
public class BlobPersistService implements FilePersistService, UploadPartPersistService
public class BlobPersistService implements UploadPartPersistService
{

@In
private Session session;
@In
private DocumentDAO documentDAO;

public HDocumentUploadPart newUploadPartFromStream(InputStream partContentStream, int contentLength)
{
Expand All @@ -64,44 +49,4 @@ public HDocumentUploadPart newUploadPartFromStream(InputStream partContentStream
return newPart;
}

public void persistRawDocumentContentFromFile(HRawDocument rawDocument, File rawFile)
{
FileInputStream tempFileStream;
try
{
tempFileStream = new FileInputStream(rawFile);
}
catch (FileNotFoundException e)
{
// TODO damason: throw more appropriate exception and handle in caller
log.error("Failed to open stream from temp source file", e);
throw new ChunkUploadException(Status.INTERNAL_SERVER_ERROR,
"Error saving uploaded document on server, download in original format may fail.\n",
e);
}
LobHelper lobHelper = documentDAO.getLobHelper();
Blob fileContents = lobHelper.createBlob(tempFileStream, (int) rawFile.length());
rawDocument.setContent(fileContents);
}

@Override
public InputStream getRawDocumentContentAsStream(HRawDocument document)
{
try
{
return document.getContent().getBinaryStream();
}
catch (SQLException e)
{
throw new RawDocumentContentAccessException(e);
}
}

@Override
public boolean hasPersistedDocument(GlobalDocumentId id)
{
HDocument doc = documentDAO.getByProjectIterationAndDocId(id.getProjectSlug(),
id.getVersionSlug(), id.getDocId());
return doc.getRawDocument() != null;
}
}
Expand Up @@ -27,6 +27,7 @@

public interface FilePersistService
{
// TODO damason: add persistRawDocumentContentFromStream(HRawDocument, InputStream)

public void persistRawDocumentContentFromFile(HRawDocument rawDocument, File rawFile);

Expand Down
155 changes: 155 additions & 0 deletions zanata-war/src/main/java/org/zanata/file/FileSystemPersistService.java
@@ -0,0 +1,155 @@
/*
* Copyright 2013, Red Hat, Inc. and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.zanata.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import lombok.extern.slf4j.Slf4j;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.zanata.ApplicationConfiguration;
import org.zanata.dao.DocumentDAO;
import org.zanata.model.HDocument;
import org.zanata.model.HProject;
import org.zanata.model.HProjectIteration;
import org.zanata.model.HRawDocument;
import org.zanata.rest.service.VirusScanner;

import com.google.common.io.Files;

@Name("filePersistService")
@Scope(ScopeType.STATELESS)
@AutoCreate
@Slf4j
public class FileSystemPersistService implements FilePersistService
{

private static final String RAW_DOCUMENTS_SUBDIRECTORY = "documents";

@In("applicationConfiguration")
private ApplicationConfiguration appConfig;
@In
private DocumentDAO documentDAO;
@In
private VirusScanner virusScanner;

@Override
public void persistRawDocumentContentFromFile(HRawDocument rawDocument, File fromFile)
{
String fileName = generateFileNameFor(rawDocument);
rawDocument.setFileId(fileName);

File newFile = getFileForName(fileName);
try
{
Files.copy(fromFile, newFile);
}
catch (IOException e)
{
// FIXME damason: throw something more specific and handle at call sites
throw new RuntimeException(e);
}

GlobalDocumentId globalId = getGlobalId(rawDocument);
log.info("Persisted raw document {} to file {}", globalId, newFile.getAbsolutePath());
virusScanner.scan(newFile, globalId.toString());
}

private File getFileForName(String fileName)
{
File docsPath = ensureDocsDirectory();
File newFile = new File(docsPath, fileName);
return newFile;
}

private File ensureDocsDirectory()
{
String basePathStringOrNull = appConfig.getDocumentFileStorageLocation();
if (basePathStringOrNull == null)
{
throw new RuntimeException("Document storage location is not configured in JNDI.");
}
File docsDirectory = new File(basePathStringOrNull, RAW_DOCUMENTS_SUBDIRECTORY);
docsDirectory.mkdirs();
return docsDirectory;
}

private static String generateFileNameFor(HRawDocument rawDocument)
{
// Could change to use id of rawDocument, and throw if rawDocument has no id yet.
String idAsString = rawDocument.getDocument().getId().toString();
String extension = rawDocument.getType().getExtension();
return idAsString + "." + extension;
}

// TODO damason: put this in a more appropriate location
private static GlobalDocumentId getGlobalId(HRawDocument rawDocument)
{
HDocument document = rawDocument.getDocument();
HProjectIteration version = document.getProjectIteration();
HProject project = version.getProject();

GlobalDocumentId id = new GlobalDocumentId(
project.getSlug(),
version.getSlug(),
document.getDocId());

return id;
}

@Override
public InputStream getRawDocumentContentAsStream(HRawDocument document) throws RawDocumentContentAccessException
{
File rawFile = getFileForRawDocument(document);
try
{
return new FileInputStream(rawFile);
}
catch (FileNotFoundException e)
{
// FIXME damason: throw more specific exception and handle at call sites
throw new RuntimeException(e);
}
}

@Override
public boolean hasPersistedDocument(GlobalDocumentId id)
{
HDocument doc = documentDAO.getByGlobalId(id);
HRawDocument rawDocument = doc.getRawDocument();
return rawDocument != null
&& getFileForRawDocument(rawDocument).exists();
}

private File getFileForRawDocument(HRawDocument rawDocument)
{
return getFileForName(rawDocument.getFileId());
}

}
Expand Up @@ -13,4 +13,10 @@ public class GlobalDocumentId
@NonNull private final String projectSlug;
@NonNull private final String versionSlug;
@NonNull private final String docId;

@Override
public String toString()
{
return projectSlug + ":" + versionSlug + ":" + docId;
}
}
Expand Up @@ -72,7 +72,7 @@ public class SourceDocumentUpload

@In(create = true, value = "documentUploadUtil")
private DocumentUploadUtil util;
@In("blobPersistService")
@In("filePersistService")
private FilePersistService filePersistService;
@In
private ZanataIdentity identity;
Expand Down

0 comments on commit 8ebb8fd

Please sign in to comment.