Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / files / AppEngineFile.java
blob577793fe5c10aafe87a08d2821ab9d7af14f5056
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;
12 /**
13 * An {@code AppEngineFile} represents a file in one of the Google App Engine
14 * file systems.
15 * <p>
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.
21 * <p>
22 * The enum {@link FileSystem} represents the available file systems. Each file
23 * system has particular attributes regarding permanence, reliability
24 * availability, and cost.
25 * <p>
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
29 * respectively.
30 * <p>
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
34 * longer be written.
35 * <p>
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}.
40 * <p>
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}.
48 public class AppEngineFile implements Serializable {
50 private static final String fullPathRegex = "^/([^/]+)/(.+)$";
51 private static final Pattern fullPathPattern = Pattern.compile(fullPathRegex);
53 /**
54 * Represents the back-end storage location of a file.
56 public static enum FileSystem {
57 /**
58 * This file system stores files as blobs in the App Engine BlobStore. The
59 * full path of a file from this file system is of the form
60 * {@code /blobstore/<identifier>} where {@code <identifier>} is an opaque String
61 * generated by the BlobStore.
63 BLOBSTORE("blobstore"),
65 /**
66 * This file system stores files in Google Storage.
67 * Files in this file system use one path for writing and one path for
68 * reading. The full path for a writable GS file is {@code /gs/<identifier>}
69 * where {@code <identifier>} is an opaque String generated by Google Storage. The
70 * full path for a readable GS file is {@code /gs/<bucket>/<key>} where
71 * {@code <bucket>} and {@code <key>} are user-specified names. See comments at
72 * the top of {@link FileService}.
74 GS("gs");
76 private String name;
78 private FileSystem(String fsn) {
79 this.name = fsn;
82 /**
83 * Returns the name of the file system.
85 public String getName() {
86 return name;
89 /**
90 * Returns the {@code FileSystem} with the given name.
92 * @throws IllegalArgumentException if the given name is not the name of any
93 * of the file systems.
95 public static FileSystem fromName(String name) {
96 for (FileSystem fs : FileSystem.values()) {
97 if (fs.getName().equals(name)) {
98 return fs;
101 throw new IllegalArgumentException(name + " is not the name of a file system.");
105 private String namePart;
106 private String fullPath;
107 private FileSystem fileSystem;
109 private BlobKey cachedBlobKey;
112 * Constructs an {@code AppEngineFile} from the given data
114 * @param fileSystem a {@code non-null FileSystem}.
115 * @param namePart a {@code non-null} name part. Warning: Do not use the full
116 * path here.
118 public AppEngineFile(FileSystem fileSystem, String namePart) {
119 this("/" + fileSystem.getName() + "/" + checkNamePart(namePart), fileSystem, namePart);
122 private static String checkNamePart(String namePart) {
123 Preconditions.checkNotNull(namePart, "namePart");
124 namePart = namePart.trim();
125 Preconditions.checkArgument(!namePart.isEmpty(), "namePart is empty");
126 return namePart;
130 * Constructs an {@code AppEngineFile} from the given data
132 * @param fullPath a {@code non-null} full path. Warning: Do not use a name
133 * part here.
135 public AppEngineFile(String fullPath) {
136 this(fullPath, null, null);
140 * Constructs an {@code AppEngineFile} from the given data.
142 * @param fullPath the {@code non-null} full path.
143 * @param fileSystem if this is {@code null} it will be parsed from {@code
144 * fullPath} and an {@code IllegalArgumentException} will be thrown if
145 * the parsing fails. If this is not {@code null} it is the caller's
146 * responsibility to ensure that it matches {@code fullPath}, no
147 * checking will be done.
148 * @param namePart The same comment from {@code fileSystem} applies to this
149 * parameter.
151 private AppEngineFile(String fullPath, FileSystem fileSystem, String namePart) {
152 Preconditions.checkNotNull(fullPath, "fullPath");
153 fullPath = fullPath.trim();
154 Preconditions.checkArgument(!fullPath.isEmpty(), "fullPath is empty");
155 this.namePart = namePart;
156 this.fullPath = fullPath;
157 this.fileSystem = fileSystem;
158 if (null == fileSystem || null == namePart) {
159 parseFullPath();
164 * Throws {@code IllegalArgumentException} if {@code fullPath} cannot be
165 * parsed into a file system and a name part.
167 private void parseFullPath() {
168 Matcher m = fullPathPattern.matcher(fullPath);
169 if (!m.matches()) {
170 throw new IllegalArgumentException(fullPath + " is not a valid path");
172 String fileSystemString = m.group(1);
173 fileSystem = FileSystem.fromName(fileSystemString);
174 namePart = m.group(2);
178 * Returns the name part of the file.
180 public String getNamePart() {
181 return namePart;
185 * Returns the full path of the file.
187 public String getFullPath() {
188 return fullPath;
192 * Returns the file system of the file.
194 public FileSystem getFileSystem() {
195 return fileSystem;
198 @Override
199 public String toString() {
200 return fullPath;
203 BlobKey getCachedBlobKey(){
204 return cachedBlobKey;
207 void setCachedBlobKey(BlobKey key){
208 this.cachedBlobKey = key;
212 * Returns a boolean indicating whether or not this instance can be used for
213 * writing.
215 public boolean isWritable() {
216 if (fileSystem == FileSystem.GS) {
217 return namePart.startsWith(FileServiceImpl.GS_CREATION_HANDLE_PREFIX);
219 return true;
223 * Returns a boolean indicating whether or not this instance can be used for
224 * reading.
226 public boolean isReadable() {
227 if (fileSystem == FileSystem.GS) {
228 return !namePart.startsWith(FileServiceImpl.GS_CREATION_HANDLE_PREFIX);
230 return true;
234 * @return a boolean indicating whether or not this instance has a finalized
235 * filename.
237 public boolean hasFinalizedName() {
238 return !namePart.startsWith(FileServiceImpl.CREATION_HANDLE_PREFIX) &&
239 !namePart.startsWith(FileServiceImpl.GS_CREATION_HANDLE_PREFIX);