001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014: * License for the specific language governing permissions and limitations
015: * under the License.
016: *
017: */
018:
019: /*
020: * Created on Oct 19, 2004
021: */
022: package org.apache.jmeter.services;
023:
024: import java.io.BufferedReader;
025: import java.io.BufferedWriter;
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.FileOutputStream;
029: import java.io.IOException;
030: import java.io.InputStreamReader;
031: import java.io.OutputStreamWriter;
032: import java.io.Reader;
033: import java.io.Writer;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.Map;
037: import java.util.Random;
038:
039: import org.apache.jmeter.gui.JMeterFileFilter;
040: import org.apache.jmeter.util.JMeterUtils;
041: import org.apache.jorphan.logging.LoggingManager;
042: import org.apache.log.Logger;
043:
044: /**
045: *
046: * The point of this class is to provide thread-safe access to files, and to
047: * provide some simplifying assumptions about where to find files and how to
048: * name them. For instance, putting supporting files in the same directory as
049: * the saved test plan file allows users to refer to the file with just it's
050: * name - this FileServer class will find the file without a problem.
051: * Eventually, I want all in-test file access to be done through here, with the
052: * goal of packaging up entire test plans as a directory structure that can be
053: * sent via rmi to remote servers (currently, one must make sure the remote
054: * server has all support files in a relative-same location) and to package up
055: * test plans to execute on unknown boxes that only have Java installed.
056: */
057: public class FileServer {
058: private static final Logger log = LoggingManager
059: .getLoggerForClass();
060:
061: private static final String DEFAULT_BASE = JMeterUtils
062: .getProperty("user.dir");
063:
064: private File base;
065:
066: //TODO - make "files" and "random" static as the class is a singleton?
067:
068: private final Map files = new HashMap();
069:
070: private static final FileServer server = new FileServer();
071:
072: private final Random random = new Random();
073:
074: private FileServer() {
075: base = new File(DEFAULT_BASE);
076: log.info("Default base=" + DEFAULT_BASE);
077: }
078:
079: public static FileServer getFileServer() {
080: return server;
081: }
082:
083: public void resetBase() throws IOException {
084: setBasedir(DEFAULT_BASE);
085: }
086:
087: public synchronized void setBasedir(String basedir)
088: throws IOException {
089: if (filesOpen()) {
090: throw new IOException(
091: "Files are still open, cannot change base directory");
092: }
093: files.clear();
094: if (basedir != null) {
095: base = new File(basedir);
096: if (!base.isDirectory()) {
097: base = base.getParentFile();
098: }
099: log.info("Set new base=" + base);
100: }
101: }
102:
103: public synchronized String getBaseDir() {
104: return base.getAbsolutePath();
105: }
106:
107: /**
108: * Creates an association between a filename and a File inputOutputObject,
109: * and stores it for later use - unless it is already stored.
110: *
111: * @param filename - relative (to base) or absolute file name
112: */
113: public synchronized void reserveFile(String filename) {
114: reserveFile(filename, null);
115: }
116:
117: /**
118: * Creates an association between a filename and a File inputOutputObject,
119: * and stores it for later use - unless it is already stored.
120: *
121: * @param filename - relative (to base) or absolute file name
122: * @param charsetName - the character set encoding to use for the file
123: */
124: public synchronized void reserveFile(String filename,
125: String charsetName) {
126: if (!files.containsKey(filename)) {
127: File f = new File(filename);
128: FileEntry file = new FileEntry(f.isAbsolute() ? f
129: : new File(base, filename), null, charsetName);
130: log.info("Stored: " + filename);
131: files.put(filename, file);
132: }
133: }
134:
135: /**
136: * Get the next line of the named file, recycle by default.
137: *
138: * @param filename
139: * @return String containing the next line in the file
140: * @throws IOException
141: */
142: public String readLine(String filename) throws IOException {
143: return readLine(filename, true);
144: }
145:
146: /**
147: * Get the next line of the named file.
148: *
149: * @param filename
150: * @param recycle - should file be restarted at EOF?
151: * @return String containing the next line in the file (null if EOF reached and not recycle)
152: * @throws IOException
153: */
154: public synchronized String readLine(String filename, boolean recycle)
155: throws IOException {
156: FileEntry fileEntry = (FileEntry) files.get(filename);
157: if (fileEntry != null) {
158: if (fileEntry.inputOutputObject == null) {
159: fileEntry.inputOutputObject = createBufferedReader(
160: fileEntry, filename);
161: } else if (!(fileEntry.inputOutputObject instanceof Reader)) {
162: throw new IOException("File " + filename
163: + " already in use");
164: }
165: BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject;
166: String line = reader.readLine();
167: if (line == null && recycle) {
168: reader.close();
169: reader = createBufferedReader(fileEntry, filename);
170: fileEntry.inputOutputObject = reader;
171: line = reader.readLine();
172: }
173: if (log.isDebugEnabled())
174: log.debug("Read:" + line);
175: return line;
176: }
177: throw new IOException("File never reserved: " + filename);
178: }
179:
180: private BufferedReader createBufferedReader(FileEntry fileEntry,
181: String filename) throws IOException {
182: FileInputStream fis = new FileInputStream(fileEntry.file);
183: InputStreamReader isr = null;
184: // If file encoding is specified, read using that encoding, otherwise use default platform encoding
185: String charsetName = fileEntry.charSetEncoding;
186: if (charsetName != null && charsetName.trim().length() > 0) {
187: isr = new InputStreamReader(fis, charsetName);
188: } else {
189: isr = new InputStreamReader(fis);
190: }
191: return new BufferedReader(isr);
192: }
193:
194: public synchronized void write(String filename, String value)
195: throws IOException {
196: FileEntry fileEntry = (FileEntry) files.get(filename);
197: if (fileEntry != null) {
198: if (fileEntry.inputOutputObject == null) {
199: fileEntry.inputOutputObject = createBufferedWriter(
200: fileEntry, filename);
201: } else if (!(fileEntry.inputOutputObject instanceof Writer)) {
202: throw new IOException("File " + filename
203: + " already in use");
204: }
205: BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject;
206: if (log.isDebugEnabled())
207: log.debug("Write:" + value);
208: writer.write(value);
209: } else {
210: throw new IOException("File never reserved: " + filename);
211: }
212: }
213:
214: private BufferedWriter createBufferedWriter(FileEntry fileEntry,
215: String filename) throws IOException {
216: FileOutputStream fos = new FileOutputStream(fileEntry.file);
217: OutputStreamWriter osw = null;
218: // If file encoding is specified, write using that encoding, otherwise use default platform encoding
219: String charsetName = fileEntry.charSetEncoding;
220: if (charsetName != null && charsetName.trim().length() > 0) {
221: osw = new OutputStreamWriter(fos, charsetName);
222: } else {
223: osw = new OutputStreamWriter(fos);
224: }
225: return new BufferedWriter(osw);
226: }
227:
228: public void closeFiles() throws IOException {
229: Iterator iter = files.entrySet().iterator();
230: while (iter.hasNext()) {
231: Map.Entry me = (Map.Entry) iter.next();
232: closeFile((String) me.getKey(), (FileEntry) me.getValue());
233: }
234: files.clear();
235: }
236:
237: /**
238: * @param name
239: * @throws IOException
240: */
241: public synchronized void closeFile(String name) throws IOException {
242: FileEntry fileEntry = (FileEntry) files.get(name);
243: closeFile(name, fileEntry);
244: }
245:
246: private void closeFile(String name, FileEntry fileEntry)
247: throws IOException {
248: if (fileEntry != null && fileEntry.inputOutputObject != null) {
249: log.info("Close: " + name);
250: if (fileEntry.inputOutputObject instanceof Reader) {
251: ((Reader) fileEntry.inputOutputObject).close();
252: } else if (fileEntry.inputOutputObject instanceof Writer) {
253: ((Writer) fileEntry.inputOutputObject).close();
254: } else {
255: log.error("Unknown inputOutputObject type : "
256: + fileEntry.inputOutputObject.getClass());
257: }
258: fileEntry.inputOutputObject = null;
259: }
260: }
261:
262: protected boolean filesOpen() {
263: Iterator iter = files.values().iterator();
264: while (iter.hasNext()) {
265: FileEntry fileEntry = (FileEntry) iter.next();
266: if (fileEntry.inputOutputObject != null) {
267: return true;
268: }
269: }
270: return false;
271: }
272:
273: /**
274: * Method will get a random file in a base directory
275: * TODO hey, not sure this method belongs here. FileServer is for threadsafe
276: * File access relative to current test's base directory.
277: *
278: * @param basedir
279: * @return a random File from the basedir that matches one of the extensions
280: */
281: public File getRandomFile(String basedir, String[] extensions) {
282: File input = null;
283: if (basedir != null) {
284: File src = new File(basedir);
285: if (src.isDirectory() && src.list() != null) {
286: File[] lfiles = src.listFiles(new JMeterFileFilter(
287: extensions));
288: int count = lfiles.length;
289: input = lfiles[random.nextInt(count)];
290: }
291: }
292: return input;
293: }
294:
295: private static class FileEntry {
296: private File file;
297: private Object inputOutputObject; // Reader/Writer
298: private String charSetEncoding;
299:
300: FileEntry(File f, Object o, String e) {
301: file = f;
302: inputOutputObject = o;
303: charSetEncoding = e;
304: }
305: }
306: }
|