mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-01-20 18:21:07 +00:00
Common files for reporting and symbol server
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@5 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
cb91a2f879
commit
68b748fc58
47
java/common/build.xml
Normal file
47
java/common/build.xml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,135 @@
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
188
java/common/src/com/google/airbag/common/CrashUtils.java
Normal file
188
java/common/src/com/google/airbag/common/CrashUtils.java
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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();
|
||||||
|
}
|
42
java/common/src/com/google/airbag/common/NameConstants.java
Normal file
42
java/common/src/com/google/airbag/common/NameConstants.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/* 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";
|
||||||
|
}
|
60
java/common/src/com/google/airbag/common/ReportQueue.java
Normal file
60
java/common/src/com/google/airbag/common/ReportQueue.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/* 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();
|
||||||
|
}
|
115
java/common/src/com/google/airbag/common/ReportQueueDirImpl.java
Normal file
115
java/common/src/com/google/airbag/common/ReportQueueDirImpl.java
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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");
|
||||||
|
}
|
||||||
|
}
|
134
java/common/src/com/google/airbag/common/ReportStorage.java
Normal file
134
java/common/src/com/google/airbag/common/ReportStorage.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
java/common/src/com/google/airbag/common/SymbolStorage.java
Normal file
86
java/common/src/com/google/airbag/common/SymbolStorage.java
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue