Deleting java/common.

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@6 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
feng.qian.moz 2006-08-29 20:26:25 +00:00
parent 68b748fc58
commit 019890bab7
16 changed files with 0 additions and 1693 deletions

View file

@ -1,47 +0,0 @@
<project name="AirbagShared" default="compile" basedir=".">
<property name="src.home" value="${basedir}/src"/>
<property name="lib.home" value="${basedir}/../third-party"/>
<property name="dist.home" value="${basedir}/dist"/>
<property name="app.name" value="airbag-common"/>
<property name="doc.home" value="${basedir}/docs"/>
<property name="build.home" value="${basedir}/build"/>
<property name="compile.debug" value="true"/>
<property name="compile.deprecation" value="true"/>
<property name="compile.optimize" value="false"/>
<path id="compile.classpath">
<pathelement location="${lib.home}/servlet-api.jar"/>
</path>
<target name="compile"
description="Compile Java sources">
<mkdir dir="${build.home}"/>
<javac srcdir="${src.home}"
destdir="${build.home}"
debug="${compile.debug}"
deprecation="${compile.deprecation}"
optimize="${compile.optimize}">
<classpath refid="compile.classpath"/>
</javac>
</target>
<target name="javadoc" depends="compile"
description="Create Javadoc API documentation">
<mkdir dir="${dist.home}/docs"/>
<javadoc sourcepath="${src.home}"
destdir="${dist.home}/docs"
packagenames="*">
<classpath refid="compile.classpath"/>
</javadoc>
</target>
<target name="dist" depends="compile,javadoc"
description="Create binary distribution">
<mkdir dir="${dist.home}/docs"/>
<!-- Create JAR file -->
<jar jarfile="${dist.home}/${app.name}.jar"
basedir="${build.home}"/>
</target>
</project>

View file

@ -1,135 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Random;
import java.util.SortedMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Common part of uploading a file with parameters. A subclass needs to
* provide a ReportStorage implementation.
*
* An upload file is saved in the file storage with parsed parameters from
* the URL.
*
* A separate processor will access the file storage and process these
* reports.
*/
@SuppressWarnings("serial")
public class AbstractMinidumpServlet extends HttpServlet {
// Minidump storage to be instantiated by subclasses in init() method.
protected ReportStorage minidumpStorage;
// Random number generator
private final Random random = new Random();
/**
* Always return success to a GET, to keep out nosy people who go
* directly to the URL.
*/
public void doGet(HttpServletRequest req, HttpServletResponse res) {
res.setStatus(HttpServletResponse.SC_OK);
}
/**
* Takes the file POSTed to the server and writes it to a file storage.
* Parameters in URL are saved as attributes of the file.
*
* @param req a wrapped HttpServletRequest that represents a multipart
* request
* @return unique ID for this report, can be used to get parameters and
* uploaded file contents from a file storage. If these is a
* collation, returns null.
*
* @throws ServletException
* @throws IOException
*/
protected String saveFile(MultipartRequest req)
throws ServletException, IOException {
// parse mutilpart request
SortedMap<String, String> params = req.getParameters();
//TODO(fqian): verify required fields of a report
InputStream inputs = req.getInputStream();
/* It is possible that two or more clients report crashes at the same
* time with same parameters. To reduce the chance of collation, we
* add two internal parameters:
* 1. reporting time, a time string in the form of YYMMDD-HHMMSS;
* 2. a random number;
*
* In theory, there is still a chance to collate, but it is very low.
* When collation happens, the one coming later is dropped.
*/
// 1. add a timestamp to parameters
params.put(NameConstants.REPORTTIME_PNAME, currentTimeString());
// 2. get a random number to make the change of collation very small
int r;
synchronized (this.random) {
r = this.random.nextInt();
}
params.put(NameConstants.RANDOMNUM_PNAME, Integer.toString(r));
String fid = this.minidumpStorage.getUniqueId(params);
assert fid != null;
if (this.minidumpStorage.reportExists(fid)) {
// collation happens
return null;
}
this.minidumpStorage.saveAttributes(fid, params);
// save uploaded contents to the storage
this.minidumpStorage.writeStreamToReport(fid, inputs, 0);
return fid;
}
/* Gets a string representing the current time using the format:
* YYMMDD-HHMMSS.
*/
private String currentTimeString() {
NumberFormat formatter = NumberFormat.getInstance();
formatter.setGroupingUsed(false);
formatter.setMinimumIntegerDigits(2); // 2 digits per date component
// All servers are in Pacific time
Calendar cal = Calendar.getInstance();
StringBuffer tstring = new StringBuffer();
tstring.append(formatter.format(cal.get(Calendar.YEAR)));
// We want January = 1.
tstring.append(formatter.format((cal.get(Calendar.MONTH) + 1)));
tstring.append(formatter.format(cal.get(Calendar.DAY_OF_MONTH)));
tstring.append("-");
tstring.append(formatter.format(cal.get(Calendar.HOUR_OF_DAY)));
tstring.append(formatter.format(cal.get(Calendar.MINUTE)));
tstring.append(formatter.format(cal.get(Calendar.SECOND)));
return new String(tstring);
}
}

View file

@ -1,188 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/** A utility class used by crash reporting server. */
public class CrashUtils {
// A map from numbers to hexadecimal characters.
private static final char[] maps = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F',
};
private static final String MD_ALGORITHM = "MD5";
// delimiter between record
private static final String RECORD_DELIMITER = ";";
private static final String FIELD_DELIMITER = " ";
private static final int BUFFER_SIZE = 1024;
/**
* Given a byte array, returns a string of its hex representation.
* Each byte is represented by two characters for its high and low
* parts. For example, if the input is [0x3F, 0x01], this method
* returns 3F01.
*
* @param bs a byte array
* @return a string of hexadecimal characters
*/
public static String bytesToHexString(byte[] bs) {
StringBuffer sb = new StringBuffer();
for (byte b : bs) {
int high = (b >> 4) & 0x0F;
int low = b & 0x0F;
sb.append(maps[high]);
sb.append(maps[low]);
}
return sb.toString();
}
/**
* Given a byte array, computes its message digest using one-way hash
* functions.
*
* @param data a byte array
* @return a string as its signature, or null if no message digest
* algorithm
* supported by the system.
*/
public static String dataSignature(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance(MD_ALGORITHM);
return bytesToHexString(md.digest(data));
} catch (NoSuchAlgorithmException e) {
return null;
}
}
/**
* Compute the signature of a file by calling dataSignature on file
* contents.
*
* @param file a file name which signature to be computed
* @return the method signature of the file, or null if failed to
* read file contents, or message digest algorithm is not supported
*/
public static String fileSignature(File file) {
try {
FileInputStream fis = new FileInputStream(file);
byte[] buf = new byte[BUFFER_SIZE];
MessageDigest md = MessageDigest.getInstance(MD_ALGORITHM);
while (true) {
int bytesRead = fis.read(buf, 0, BUFFER_SIZE);
if (bytesRead == -1)
break;
md.update(buf, 0, bytesRead);
}
return bytesToHexString(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
} catch (IOException e) {
return null;
}
}
/**
* Encodes an attribute map to a string. Encoded string format:
* name value1[ value2];name value1[ value2]
* Names and values should be escaped so that there are no
* RECORD_DELIMITER and VALUE_DELIMITER in strings.
*
* @param attributes a maps of attributes name and value to be encoded
* @return a string of encoded attributes
*/
public static
String attributesToString(SortedMap<String, String> attributes) {
StringBuffer res = new StringBuffer();
for (Map.Entry<String, String> e : attributes.entrySet()) {
String name = e.getKey();
String value = e.getValue();
assert name.indexOf(RECORD_DELIMITER) == -1;
assert name.indexOf(FIELD_DELIMITER) == -1;
res.append(name).append(FIELD_DELIMITER);
assert value.indexOf(RECORD_DELIMITER) == -1;
assert value.indexOf(FIELD_DELIMITER) == -1;
res.append(value).append(RECORD_DELIMITER);
}
return new String(res);
}
/**
* Decodes a string to a map of attributes.
*/
public static SortedMap<String, String> stringToAttributes(String s) {
SortedMap<String, String> map =
new TreeMap<String, String>();
String[] records = s.split(RECORD_DELIMITER);
for (String r : records) {
String[] fields = r.trim().split(FIELD_DELIMITER);
if (fields.length != 2) // discard records that has no values
continue;
String name = fields[0].trim();
String value = fields[1].trim();
map.put(name, value);
}
return map;
}
/**
* Copies bytes from an input stream to an output stream, with max bytes.
*
* @param ins an input stream to read
* @param outs an output stream to write
* @param max the maximum number of bytes to copy. If max <= 0, copy bytes
* until the end of input stream
* @return the number of bytes copied
* @throws IOException
*/
public static int copyStream(InputStream ins, OutputStream outs, int max)
throws IOException {
byte[] buf = new byte[BUFFER_SIZE];
int bytesWritten = 0;
while (true) {
int bytesToRead = BUFFER_SIZE;
if (max > 0)
bytesToRead = Math.min(BUFFER_SIZE, max - bytesWritten);
if (bytesToRead <= 0)
break;
int bytesRead = ins.read(buf, 0, bytesToRead);
if (bytesRead == -1) // end of input stream
break;
outs.write(buf, 0, bytesRead);
bytesWritten += bytesRead;
}
return bytesWritten;
}
}

View file

@ -1,36 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.util.SortedMap;
import java.io.InputStream;
/**
* A common interface for different multipart HttpServletRequest
* implementations. The interface is simple enough to be used by the
* upload server.
*/
public interface MultipartRequest {
/**
* Returns a sorted map of name to values of an HTTP request.
*/
public SortedMap<String, String> getParameters();
/**
* Returns an input stream of uploading file.
*/
public InputStream getInputStream();
}

View file

@ -1,42 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
/** A class defines URL parameter names. */
public class NameConstants {
// URL parameter names
// product name
public static final String PRODUCT_PNAME = "prod";
// version
public static final String VERSION_PNAME = "ver";
// application or module
public static final String APPLICATION_PNAME = "app";
// platform, e.g., win32, linux, mac
public static final String PLATFORM_PNAME = "plat";
// report format, e.g., dump, xml
public static final String FORMAT_PNAME = "fmt";
// process uptime
public static final String PROCESSUPTIME_PNAME = "procup";
// cumulative process uptime
public static final String CUMULATIVEUPTIME_PNAME = "cumup";
// time when report is created
public static final String REPORTTIME_PNAME = "rept";
// a random number
public static final String RANDOMNUM_PNAME = "rand";
// report checksum
public static final String CHECKSUM_PNAME = "sum";
}

View file

@ -1,60 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.util.LinkedList;
/**
* A queue interface for unprocessed report ids. The interface is intended
* for inter-process usage. A report uploading server enqueues new report
* ids, and a processor dequeues ids.
*
* The interface is much simpler than <b>java.util.Queue</b>. An implementation
* should provide a persistent storage of queued ids even when a process
* is killed.
*/
public interface ReportQueue {
/**
* Enqueue a record id.
*
* @param rid
* @return true if success
*/
public boolean enqueue(String rid);
/**
* Enqueue a list of ids
*
* @param ids
* @return true if success
*/
public boolean enqueue(LinkedList<String> ids);
/**
* Checks if a queue is empty
* @return true if the queue is empty
*/
public boolean empty();
/**
* Removes several ids from the queue. An implementation decides how
* many ids to be removed.
*
* @return a list of queue
*/
public LinkedList<String> dequeue();
}

View file

@ -1,115 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.util.LinkedList;
import java.io.File;
import java.io.IOException;
/**
* Implements ReportQueue using directories. Some restrictions:
* <ul>
* <li>Ids must be valid file names;</li>
* <li>Ids cannot be duplicated, a duplicated id is ignored;</li>
* <li>No guarantees on ordering, in other words, this is not really
* a queue (with FIFO order);</li>
* </ul>
*/
public class ReportQueueDirImpl implements ReportQueue {
// maximum number of ids returned by dequque method.
private static final int MAX_DEQUEUED_IDS = 100;
// the directory name for storing files
private String queueDir;
/**
* Creates an instance of ReportQueueDirImpl with a directory name.
* @param dirname
*/
public ReportQueueDirImpl(String dirname)
throws IOException
{
this.queueDir = dirname;
File q = new File(dirname);
if (!q.exists())
q.mkdirs();
if (!q.isDirectory())
throw new IOException("name "+dirname
+" exits already, but not a directory.");
}
/** Enqueue a report id. */
public boolean enqueue(String rid) {
//lock on the directory
// add a file named by id
File f = new File(this.queueDir, rid);
try {
return f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
/** Enqueue a list of ids. */
public boolean enqueue(LinkedList<String> ids) {
//lock on the directory
// add a file named by id
for (String rid : ids) {
File f = new File(this.queueDir, rid);
try {
if (!f.createNewFile())
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
return true;
}
/** Checks if the queue is empty. */
public boolean empty() {
File f = new File(this.queueDir);
String[] ids = f.list();
if (ids == null)
return true;
else
return ids.length == 0;
}
/** Remove ids from the queue. */
public LinkedList<String> dequeue() {
// lock on the directory
LinkedList<String> rids = new LinkedList<String>();
File d = new File(this.queueDir);
String[] ids = d.list();
if (ids == null)
return rids;
for (int i =0; i < Math.min(ids.length, MAX_DEQUEUED_IDS); i++) {
File f = new File(this.queueDir, ids[i]);
f.delete();
rids.add(ids[i]);
}
return rids;
}
}

View file

@ -1,126 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.LinkedList;
import java.util.logging.Logger;
/**
* Implementation of ReportQueue using a shared file. File accesses are
* protected by a file lock. When dequeue, all reports in the file were
* returned and the file is truncated to length zero.
*/
public class ReportQueueFileImpl implements ReportQueue {
private static final Logger logger =
Logger.getLogger(ReportQueueFileImpl.class.getName());
private String queueFile;
/** Given a file name, creates an instance of ReportQueueFileImpl. */
public ReportQueueFileImpl(String fname) {
this.queueFile = fname;
}
/** Enqueues a report id. */
public boolean enqueue(String rid) {
try {
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
FileChannel fc = raf.getChannel();
// block thread until lock is obtained
FileLock lock = fc.lock();
raf.seek(raf.length());
raf.writeBytes(rid);
raf.writeByte('\n');
lock.release();
fc.close();
raf.close();
return true;
} catch (FileNotFoundException e) {
logger.severe("Cannot open file "+this.queueFile+" for write.");
} catch (IOException e) {
logger.severe("Cannot write to file "+this.queueFile);
}
return false;
}
/** Enqueues a list of report ids. */
public boolean enqueue(LinkedList<String> ids) {
try {
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
FileChannel fc = raf.getChannel();
// block thread until lock is obtained
FileLock lock = fc.lock();
raf.seek(raf.length());
for (String rid : ids) {
raf.writeBytes(rid);
raf.writeByte('\n');
}
lock.release();
fc.close();
raf.close();
return true;
} catch (FileNotFoundException e) {
logger.severe("Cannot open file "+this.queueFile+" for write.");
} catch (IOException e) {
logger.severe("Cannot write to file "+this.queueFile);
}
return false;
}
public boolean empty() {
// check the length of the file
File f = new File(this.queueFile);
return f.length() == 0L;
}
public LinkedList<String> dequeue() {
LinkedList<String> ids = new LinkedList<String>();
try {
RandomAccessFile raf = new RandomAccessFile(this.queueFile, "rw");
FileChannel fc = raf.getChannel();
FileLock flock = fc.lock();
while (true) {
String s = raf.readLine();
if (s == null)
break;
s = s.trim();
if (s.equals(""))
continue;
ids.add(s);
}
fc.truncate(0L);
// release the lock
flock.release();
fc.close();
raf.close();
} catch (FileNotFoundException e) {
logger.severe("Cannot open file "+this.queueFile+" for write.");
} catch (IOException e) {
logger.severe("Cannot write to file "+this.queueFile);
}
return ids;
}
}

View file

@ -1,62 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.IOException;
import java.util.LinkedList;
/**
* A test class for ReportQueue implementations, currently tests
* ReportQueueDirImpl and ReportQueueFileImpl.
*/
public class ReportQueueTest {
public static void main(String[] args) {
ReportQueue rq = new ReportQueueFileImpl("/tmp/rqtest");
runTest(rq);
try {
rq = new ReportQueueDirImpl("/tmp/rqdir");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
runTest(rq);
System.out.println("OK");
}
private static void runTest(ReportQueue rq) {
rq.enqueue("hello");
rq.enqueue("world");
LinkedList<String> v = rq.dequeue();
assert v.size() == 2;
assert v.get(0).equals("hello");
assert v.get(1).equals("world");
assert rq.empty();
v.remove();
rq.enqueue(v);
assert !rq.empty();
v = rq.dequeue();
assert v.size() == 1;
assert v.get(0).equals("world");
}
}

View file

@ -1,134 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.SortedMap;
/**
* ReportStorage.java
*
* <p>
* Provide an abstract layer for storing crash reports and associated meta
* data.
* </p>
*
* <p>
* The interface is intended to be used by a client in the following way:
*
* <pre>
* ReportStorage rs = new ReportStorageImpl(...);
* // Write an uploading file to a storage
* try {
* String rid = rs.getUniqueId(attributes); // params from URL
* rs.saveAttributes(id, attributes);
* if (!rs.reportExists(rid) || allowOverwrite)
* rs.writeStreamToReport(rid, input, 0);
* else
* rs.appendStreamToReport(rid, input, 0);
* } catch (...)
*
* // Read a file from the storage
* try {
* OutputStream os = fs.openReportForRead(fid);
* os.read(...);
* os.close();
* } catch (...)
* </pre>
*/
public interface ReportStorage {
/**
* Given a sorted map of attributes (name and value), returns a unique id
* of the crash report.
*
* @param params a sorted map from name to value
* @return a string as the file id
*/
public String getUniqueId(SortedMap<String, String> params);
/**
* Given a report id, checks if the report data AND attributes identified
* by this id exists on the storage.
*
* @param id a report id
* @return true if the id represents an existing file
*/
public boolean reportExists(String id);
/**
* Given a report id and a sorted map of attributes, saves attributes on
* the storage.
*
* @param id a report id
* @param attrs attributes associated with this id
* @return true if attributes are saved successfully
*/
public boolean saveAttributes(String id, SortedMap<String, String> attrs);
/**
* Given a report id, returns attributes associated with this report.
*
* @param id a report id
* @return a sorted map from name to value
* @throws FileNotFoundException if fileExists(id) returns false
*/
public SortedMap<String, String> getAttributes(String id)
throws FileNotFoundException;
/**
* Writes <i>max</i> bytes from an input stream to a report identified by
* an <i>id</i>.
*
* @param id a report id
* @param input an input stream
* @param max
* maximum bytes to be written, if max is less or equal than 0, it will
* write all bytes from input stream to the file
* @return the number of bytes written
* @throws FileNotFoundException
* if reportExists(id) returns false
* @throws IOException
*/
public int writeStreamToReport(String id, InputStream input, int max)
throws FileNotFoundException, IOException;
/**
* Opens a report for read.
*
* @param id a report id
* @return an output stream for read
* @throws FileNotFoundException
*/
public InputStream openReportForRead(String id) throws FileNotFoundException;
/**
* @param id a report id
* @return the checksum of a report.
* @throws FileNotFoundException
*/
public String getChecksum(String id) throws FileNotFoundException;
/**
* Removes a report.
*
* @param id a report id
* @return true if the report is removed successfully
*/
public boolean removeReport(String id) throws FileNotFoundException;
}

View file

@ -1,197 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.util.SortedMap;
import java.util.logging.Logger;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
/**
* <p>Implement FileStorage using a local file system.</p>
*
* <p>Given a sorted map of attributes, create a checksum as unique file
* id.</p>
*
* <p>Each file id is associated with two files in the storage:
* <ol>
* <li>an attribute file named as <id>.attr;</li>
* <li>a data file named as <id>.data;</li>
* </ol>
* </p>
*/
public class ReportStorageLocalFileImpl implements ReportStorage {
// Name extension of attribute file
private static final String ATTR_FILE_EXT = ".attr";
// Name extension of data file
private static final String DATA_FILE_EXT = ".data";
// Set the maximum file length at 1M
private static final long MAX_ATTR_FILE_LENGTH = 1 << 10;
// Logging
private static final Logger logger =
Logger.getLogger(ReportStorageLocalFileImpl.class.getName());
// Directory name for storing files.
private String directoryName;
/**
* Creates an instance of ReportStorageLocalFileImpl by providing a
* directory name.
*
* @param dirname a directory for storing files
* @throws IOException
* @throws NullPointerException if dirname is null
*/
public ReportStorageLocalFileImpl(String dirname) throws IOException {
this.directoryName = dirname;
// new File can throw NullPointerException if dirname is null
File dir = new File(dirname);
if (!dir.exists() && !dir.mkdirs())
throw new IOException("Cannot make dirs for "+dirname);
if (!dir.canWrite())
throw new IOException("Cannot write to "+dirname
+", check your permissions.");
}
/**
* Returns a hashed string of attributes. Attributes are saved
* in the file storage if not exists.
*/
public String getUniqueId(SortedMap<String, String> attributes)
{
String attr = CrashUtils.attributesToString(attributes);
return CrashUtils.dataSignature(attr.getBytes());
}
/**
* Saves attributes associated with a given id. if attributes does not
* match the id (comparing results of getUniqueId(attributes)
* with id), it returns false. Otherwise, attributes are saved.
*/
public boolean saveAttributes(String id, SortedMap<String, String> attributes)
{
String attr = CrashUtils.attributesToString(attributes);
String digest = CrashUtils.dataSignature(attr.getBytes());
if (!digest.equals(id))
return false;
try {
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
// check if attr file exists
if (!attrFile.exists()) {
FileOutputStream fos = new FileOutputStream(attrFile);
fos.write(attr.getBytes());
fos.close();
}
return true;
} catch (IOException e) {
e.printStackTrace();
logger.warning(e.toString());
return false;
}
}
/** Checks if a report id exists. */
public boolean reportExists(String id) {
File datafile = new File(this.directoryName, id+DATA_FILE_EXT);
File attrfile = new File(this.directoryName, id+ATTR_FILE_EXT);
return datafile.isFile() && attrfile.isFile();
}
/** Returns attributes in a map associated with an id. */
public SortedMap<String, String> getAttributes(String id)
throws FileNotFoundException
{
if (!this.reportExists(id))
throw new FileNotFoundException("no file is identified by "+id);
File attrfile = new File(this.directoryName, id+ATTR_FILE_EXT);
if (!attrfile.isFile())
throw new FileNotFoundException("no file is identified by "+id);
int length = (int) attrfile.length();
if (length >= MAX_ATTR_FILE_LENGTH)
throw new FileNotFoundException("no file is identified by "+id);
byte[] content = new byte[length];
try {
FileInputStream fis = new FileInputStream(attrfile);
fis.read(content);
fis.close();
// verify checksum
String sig = CrashUtils.dataSignature(content);
if (!sig.equals(id)) {
logger.warning("illegal access to "+attrfile);
return null;
}
// parse contents
return CrashUtils.stringToAttributes(new String(content));
} catch (IOException e) {
logger.warning(e.toString());
return null;
}
}
public int writeStreamToReport(String id, InputStream input, int max)
throws FileNotFoundException, IOException {
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
FileOutputStream fos = new FileOutputStream(datafile);
int bytesCopied = CrashUtils.copyStream(input, fos, max);
fos.close();
return bytesCopied;
}
public InputStream openReportForRead(String id) throws FileNotFoundException {
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
return new FileInputStream(datafile);
}
public String getChecksum(String id) throws FileNotFoundException {
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
return CrashUtils.fileSignature(datafile);
}
public boolean removeReport(String id) {
File datafile = new File(this.directoryName, id + DATA_FILE_EXT);
File attrfile = new File(this.directoryName, id + ATTR_FILE_EXT);
datafile.delete();
attrfile.delete();
return true;
}
}

View file

@ -1,89 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.TreeMap;
/** A simple regression test of ReportStorage.java and implementations.
*/
public class ReportStorageTest {
public static void main(String[] args) {
// use /tmp/test as testing directory
ReportStorage rs = null;
try {
rs = new ReportStorageLocalFileImpl("/tmp/test");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
runTest(rs);
// test passed
System.out.println("OK.");
}
private static void runTest(ReportStorage rs) {
// test CrashUtil.bytesToHexString
byte[] ba = new byte[4];
ba[0] = (byte)0xFF;
ba[1] = (byte)0x00;
ba[2] = (byte)0x0F;
ba[3] = (byte)0xF0;
String s = CrashUtils.bytesToHexString(ba);
assert s.equals("FF000FF0");
// construct a simple map of attributes
TreeMap<String, String> params =
new TreeMap<String, String>();
params.put("Hello", "World");
String rid = rs.getUniqueId(params);
assert rid != null;
boolean b = rs.saveAttributes(rid, params);
assert b;
ba = "hellow, world!".getBytes();
InputStream in = new ByteArrayInputStream(ba);
assert rs.reportExists(rid);
// save contents to storage
try {
rs.writeStreamToReport(rid, in, 0);
} catch (FileNotFoundException e) {
e.printStackTrace();
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// read contents out
try {
InputStream in1 = rs.openReportForRead(rid);
assert in1.available() == ba.length;
in1.read(ba);
assert(new String(ba).equals("hellow, world!"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -1,86 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.IOException;
import java.io.InputStream;
import java.util.SortedMap;
/**
* <p>SymbolStorage provides a simple interface for storing and retrieving
* symbol files. Symbol files are indexed by a set of attributes:
* <ul>
* <li>product name</li>
* <li>version (build id)</li>
* <li>platform</li>
* <li>application/module name</li>
* </ul>
* </p>
*
* <p>
* The interface is designed for a symbol server supports upload, getid,
* download operations.
* </p>
*/
public interface SymbolStorage {
/**
* Saves a symbol file indexed by a set of attributes. Attributes include
* product, version, platform, application/module, plus a checksum of
* the symbol file.
*
* If a symbol file whose checksum matches the attribute, the input stream
* can be NULL. No contents will be written. This can save some workloads
* of uploading symbols.
*
* @param attrs a map of attributes, it must have 'prod', 'ver', 'plat',
* 'app', and 'sum', values of the first four attributes are used
* as index, and the value of the last attribute is the MD5 checksum
* of the symbol file for verification.
* @param contents symbol file contents.
* @return true if checksum matches
* @throws IOException
*/
public boolean saveSymbolFile(SortedMap<String, String> attrs,
InputStream contents) throws IOException;
/**
* Gets the checksum of a symbol file indexed by a set of attributes.
* @param attrs a map of attributes, must include 'prod', 'ver', 'plat',
* and 'app'.
* @return MD5 checksum as a string
* @throws IOException
*/
public String getFileChecksum(SortedMap<String, String> attrs)
throws IOException;
/**
* Checks if a file exists already (identified by its checksum).
* @param checksum the file checksum
* @return true if a file with the same checksum exists
*/
public boolean fileExists(String checksum);
/**
* Gets an input stream of a symbol server indexed by a set of attributes.
* @param attrs a map of attributes, must include 'prod', 'ver', 'plat',
* and 'app'.
* @return an input stream of the symbol file
* @throws IOException
*/
public InputStream openFileForRead(SortedMap<String, String> attrs)
throws IOException;
}

View file

@ -1,203 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Implementation of SymbolStorage interface using a local file system.
*
* Given a set of parameters, prod, ver, app, plat, computes its MD5 digest.
* Digest + .attr contains attributes and checksum of the symbol file.
* Symbol file content is stored in a file named by its checksum + .data.
*
* To upload a symbol file, a client can send a query:
* <pre>
* /symexists?sum=<checksum>, the server checks if a symbol file identified by checksum exists;
* /upload?prod=<product>&ver=<version>&plat=<platform>&app=<module>&sum=<checksum> use POST
* method with or without uploading a file.
*
* A client can always call /upload to upload a symbol file.
* However, a more efficient way is to check whether a file is on the server by calling /symexists.
* If so, the client can just POST the request without actually upload the file content,
* checksum is sufficient.
*
* /getchecksum?prod=<product>&ver=<version>&plat=<platform>&app=<module>, returns the checksum
* of the symbol file on the server.
* /download?prod=<product>&ver=<version>&plat=<platform>&app=<module>, downloads the symbol file
* on the server.
*
* A client can always use /download to download a symbol file.
* However, if the client maintins its own cache of symbol files, it can call /getchecksum,
* and look up the cache using the checksum. If the cache does not have the file, then it
* calls /download.
* </pre>
*/
public class SymbolStorageLocalFileImpl implements SymbolStorage {
// Name extension of attribute file
private static final String ATTR_FILE_EXT = ".attr";
// Name extension of data file
private static final String DATA_FILE_EXT = ".data";
private static final int MAX_ATTR_FILE_LENGTH = 1 << 10;
// Directory name for storing files.
private String directoryName;
/**
* Creates an instance of ReportStorageLocalFileImpl by providing a
* directory name.
*
* @param dirname
* @throws IOException
*/
public SymbolStorageLocalFileImpl(String dirname) throws IOException {
this.directoryName = dirname;
// new File can throw NullPointerException if dirname is null
File dir = new File(dirname);
if (!dir.exists() && !dir.mkdirs())
throw new IOException("Cannot make dirs for "+dirname);
if (!dir.canWrite())
throw new IOException("Cannot write to "+dirname
+", check your permissions.");
}
/**
* Save a symbol file.
*/
public boolean saveSymbolFile(SortedMap<String, String> attrs, InputStream contents)
throws IOException {
String digest = getAttributesSignature(attrs);
// get 'sum' value
String checksum = attrs.get(NameConstants.CHECKSUM_PNAME);
if (checksum == null)
return false;
// attribute file name and data file name
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
// use passed in checksum as file name
File dataFile = new File(this.directoryName, checksum+DATA_FILE_EXT);
// write data to file
FileOutputStream outs = new FileOutputStream(dataFile);
CrashUtils.copyStream(contents, outs, 0);
outs.close();
// get signature of input stream
String filesig = CrashUtils.fileSignature(dataFile);
if (!checksum.equals(filesig)) {
dataFile.delete();
return false;
}
// save all attributes with checksum
String fullAttrs = CrashUtils.attributesToString(attrs);
FileOutputStream fos = new FileOutputStream(attrFile);
fos.write(fullAttrs.getBytes());
fos.close();
return true;
}
public String getFileChecksum(SortedMap<String, String> attrs) throws IOException {
String digest = getAttributesSignature(attrs);
File attrFile = new File(this.directoryName, digest+ATTR_FILE_EXT);
if (!attrFile.isFile())
throw new FileNotFoundException();
int length = (int) attrFile.length();
if (length >= MAX_ATTR_FILE_LENGTH)
throw new FileNotFoundException();
byte[] content = new byte[length];
FileInputStream fis = new FileInputStream(attrFile);
fis.read(content);
fis.close();
// parse contents
SortedMap<String, String> savedAttrs =
CrashUtils.stringToAttributes(new String(content));
return savedAttrs.get(NameConstants.CHECKSUM_PNAME);
}
public boolean fileExists(String checksum) {
File dataFile = new File(this.directoryName, checksum+DATA_FILE_EXT);
return dataFile.isFile();
}
public InputStream openFileForRead(SortedMap<String, String> attrs)
throws IOException {
String checksum = getFileChecksum(attrs);
if (checksum == null)
throw new FileNotFoundException();
File dataFile = new File(this.directoryName, checksum + DATA_FILE_EXT);
if (!dataFile.isFile())
throw new FileNotFoundException();
return new FileInputStream(dataFile);
}
private static final String[] requiredParameters = {
NameConstants.PRODUCT_PNAME,
NameConstants.APPLICATION_PNAME,
NameConstants.PLATFORM_PNAME,
NameConstants.VERSION_PNAME
};
private String getAttributesSignature(SortedMap<String, String> attrs) {
// canonize parameters
SortedMap<String, String> params = canonizeAttributes(attrs);
String attrString = CrashUtils.attributesToString(params);
return CrashUtils.dataSignature(attrString.getBytes());
}
/* Canonize attributes, get 'prod', 'ver', 'plat', and 'app' values,
* and put them in a new sorted map. If one of value is missing,
* returns null.
*/
private SortedMap<String, String>
canonizeAttributes(SortedMap<String, String> attrs) {
SortedMap<String, String> params = new TreeMap<String, String>();
for (String s : requiredParameters) {
String v = attrs.get(s);
if (v == null)
return null;
else
params.put(s, v);
}
return params;
}
}

View file

@ -1,87 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.TreeMap;
/**
* Test ReportStorage and its implementation by simulating two pocesses
* as producer and consuer.
*
* To use this test with TestReportStorageProducer:
* > java TestReportStorageProducer /tmp/testdir
*
* In another console,
* > java TestReportStorageConsumer /tmp/testdir
*
* Then watch output on both console.
*/
public class TestReportStorageConsumer {
/**
* @param args
*/
public static void main(String[] args) {
String testdir = args[0];
ReportStorage rs = null;
try {
rs = new ReportStorageLocalFileImpl(testdir);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
consume(rs);
}
private static void consume(ReportStorage rs) {
int i = 0;
TreeMap<String, String> params =
new TreeMap<String, String>();
String v = "hello";
byte[] buf = new byte[1024];
while (true) {
params.put(Integer.toString(i), v);
String id = rs.getUniqueId(params);
rs.saveAttributes(id, params);
if (rs.reportExists(id)) {
InputStream is = null;
try {
is = rs.openReportForRead(id);
while (is.read(buf) != -1) {
System.out.print(new String(buf));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
}
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}

View file

@ -1,86 +0,0 @@
/* Copyright (C) 2006 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.airbag.common;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.TreeMap;
/**
* Test ReportStorage and its implementation by simulating two pocesses
* as producer and consuer.
*
* To use this test with TestReportStorageConsumer:
* > java TestReportStorageProducer /tmp/testdir
*
* In another console,
* > java TestReportStorageConsumer /tmp/testdir
*
* Then watch output on both console.
*/
public class TestReportStorageProducer {
/**
* @param args
*/
public static void main(String[] args) {
String testdir = args[0];
ReportStorage rs = null;
try {
rs = new ReportStorageLocalFileImpl(testdir);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
produce(rs);
}
private static void produce(ReportStorage rs) {
int i = 0;
TreeMap<String, String> params =
new TreeMap<String, String>();
String v = "hello";
ByteArrayInputStream ba = new ByteArrayInputStream("hello world!".getBytes());
while (true) {
ba.reset();
params.put(Integer.toString(i), v);
String id = rs.getUniqueId(params);
rs.saveAttributes(id, params);
if (id == null) {
System.exit(1);
}
try {
rs.writeStreamToReport(id, ba, 0);
} catch (FileNotFoundException e) {
e.printStackTrace();
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}