1 // Copyright 2010 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.files
;
5 import com
.google
.appengine
.api
.blobstore
.BlobKey
;
6 import com
.google
.common
.base
.Preconditions
;
8 import java
.io
.Serializable
;
9 import java
.util
.regex
.Matcher
;
10 import java
.util
.regex
.Pattern
;
13 * An {@code AppEngineFile} represents a file in one of the Google App Engine
16 * A file has a <b>path</b> of the form {@code /<fileSystem>/<namePart> }. The
17 * path consists of a <b>file system</b> which is one of several identifiers for
18 * a Google App Engine file system, and a <b>name part</b> wich is an arbitrary
19 * String. For example "/blobstore/Aie7uHVwtvM" is a path in which "blobstore"
20 * is the file system and "Aie7uHVwtvM" is the name part.
22 * The enum {@link FileSystem} represents the available file systems. Each file
23 * system has particular attributes regarding permanence, reliability
24 * availability, and cost.
26 * In the current release of Google App Engine, {@link FileSystem#BLOBSTORE
27 * BLOBSTORE} and {@link FileSystem#GS GS} are the only available file systems.
28 * These file systems store files as blobs in the BlobStore and in Google Storage
31 * App Engine files may only be accessed using a particular access pattern:
32 * Newly created files may be <b>appended</b> to until they are
33 * <b>finalized</b>. After a file is finalized it may be read, but it may no
36 * To create a new {@code BLOBSTORE} file use
37 * {@link FileService#createNewBlobFile(String)}. This returns an instance of
38 * {@code AppEngineFile} with a {@code FileSystem} of {@code BLOBSTORE}.
41 * To create a new {@code GS} file use
42 * {@link FileService#createNewGSFile(GSFileOptions)}. This returns an
43 * instance of {@code AppEngineFile} with a {@code FileSystem} of {@code GS}.
44 * This instance cannot be used for reading. For a full file lifecycle
45 * example, see {@link FileService}.
49 public class AppEngineFile
implements Serializable
{
51 private static final String fullPathRegex
= "^/([^/]+)/(.+)$";
52 private static final Pattern fullPathPattern
= Pattern
.compile(fullPathRegex
);
55 * Represents the back-end storage location of a file.
57 public static enum FileSystem
{
59 * This file system stores files as blobs in the App Engine BlobStore. The
60 * full path of a file from this file system is of the form
61 * {@code /blobstore/<identifier>} where {@code <identifier>} is an opaque String
62 * generated by the BlobStore.
64 BLOBSTORE("blobstore"),
67 * This file system stores files in Google Storage.
68 * Files in this file system use one path for writing and one path for
69 * reading. The full path for a writable GS file is {@code /gs/<identifier>}
70 * where {@code <identifier>} is an opaque String generated by Google Storage. The
71 * full path for a readable GS file is {@code /gs/<bucket>/<key>} where
72 * {@code <bucket>} and {@code <key>} are user-specified names. See comments at
73 * the top of {@link FileService}.
79 private FileSystem(String fsn
) {
84 * Returns the name of the file system.
86 public String
getName() {
91 * Returns the {@code FileSystem} with the given name.
93 * @throws IllegalArgumentException if the given name is not the name of any
94 * of the file systems.
96 public static FileSystem
fromName(String name
) {
97 for (FileSystem fs
: FileSystem
.values()) {
98 if (fs
.getName().equals(name
)) {
102 throw new IllegalArgumentException(name
+ " is not the name of a file system.");
106 private String namePart
;
107 private String fullPath
;
108 private FileSystem fileSystem
;
110 private BlobKey cachedBlobKey
;
113 * Constructs an {@code AppEngineFile} from the given data
115 * @param fileSystem a {@code non-null FileSystem}.
116 * @param namePart a {@code non-null} name part. Warning: Do not use the full
119 public AppEngineFile(FileSystem fileSystem
, String namePart
) {
120 this("/" + fileSystem
.getName() + "/" + checkNamePart(namePart
), fileSystem
, namePart
);
123 private static String
checkNamePart(String namePart
) {
124 Preconditions
.checkNotNull(namePart
, "namePart");
125 namePart
= namePart
.trim();
126 Preconditions
.checkArgument(!namePart
.isEmpty(), "namePart is empty");
131 * Constructs an {@code AppEngineFile} from the given data
133 * @param fullPath a {@code non-null} full path. Warning: Do not use a name
136 public AppEngineFile(String fullPath
) {
137 this(fullPath
, null, null);
141 * Constructs an {@code AppEngineFile} from the given data.
143 * @param fullPath the {@code non-null} full path.
144 * @param fileSystem if this is {@code null} it will be parsed from {@code
145 * fullPath} and an {@code IllegalArgumentException} will be thrown if
146 * the parsing fails. If this is not {@code null} it is the caller's
147 * responsibility to ensure that it matches {@code fullPath}, no
148 * checking will be done.
149 * @param namePart The same comment from {@code fileSystem} applies to this
152 private AppEngineFile(String fullPath
, FileSystem fileSystem
, String namePart
) {
153 Preconditions
.checkNotNull(fullPath
, "fullPath");
154 fullPath
= fullPath
.trim();
155 Preconditions
.checkArgument(!fullPath
.isEmpty(), "fullPath is empty");
156 this.namePart
= namePart
;
157 this.fullPath
= fullPath
;
158 this.fileSystem
= fileSystem
;
159 if (null == fileSystem
|| null == namePart
) {
165 * Throws {@code IllegalArgumentException} if {@code fullPath} cannot be
166 * parsed into a file system and a name part.
168 private void parseFullPath() {
169 Matcher m
= fullPathPattern
.matcher(fullPath
);
171 throw new IllegalArgumentException(fullPath
+ " is not a valid path");
173 String fileSystemString
= m
.group(1);
174 fileSystem
= FileSystem
.fromName(fileSystemString
);
175 namePart
= m
.group(2);
179 * Returns the name part of the file.
181 public String
getNamePart() {
186 * Returns the full path of the file.
188 public String
getFullPath() {
193 * Returns the file system of the file.
195 public FileSystem
getFileSystem() {
200 public String
toString() {
204 BlobKey
getCachedBlobKey(){
205 return cachedBlobKey
;
208 void setCachedBlobKey(BlobKey key
){
209 this.cachedBlobKey
= key
;
213 * Returns a boolean indicating whether or not this instance can be used for
216 public boolean isWritable() {
217 if (fileSystem
== FileSystem
.GS
) {
218 return namePart
.startsWith(FileServiceImpl
.GS_CREATION_HANDLE_PREFIX
);
224 * Returns a boolean indicating whether or not this instance can be used for
227 public boolean isReadable() {
228 if (fileSystem
== FileSystem
.GS
) {
229 return !namePart
.startsWith(FileServiceImpl
.GS_CREATION_HANDLE_PREFIX
);
235 * @return a boolean indicating whether or not this instance has a finalized
238 public boolean hasFinalizedName() {
239 return !namePart
.startsWith(FileServiceImpl
.CREATION_HANDLE_PREFIX
) &&
240 !namePart
.startsWith(FileServiceImpl
.GS_CREATION_HANDLE_PREFIX
);