/Microsoft.Samples.BizTalk.Adapter.Tcp/Microsoft.Samples.BizTalk.Adapter.Tcp/VirtualStream.cs

# · C# · 414 lines · 198 code · 49 blank · 167 comment · 27 complexity · bb169eb56d22c989da213bd75e538621 MD5 · raw file

  1. //---------------------------------------------------------------------
  2. // File: VirtualStream.cs
  3. //
  4. // Summary: A sample pipeline component which demonstrates how to promote message context
  5. // properties and write distinguished fields for XML messages using arbitrary
  6. // XPath expressions.
  7. //
  8. // Sample: Arbitrary XPath Property Handler Pipeline Component SDK
  9. //
  10. //---------------------------------------------------------------------
  11. // This file is part of the Microsoft BizTalk Server 2006 SDK
  12. //
  13. // Copyright (c) Microsoft Corporation. All rights reserved.
  14. //
  15. // This source code is intended only as a supplement to Microsoft BizTalk
  16. // Server 2006 release and/or on-line documentation. See these other
  17. // materials for detailed information regarding Microsoft code samples.
  18. //
  19. // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  20. // KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  21. // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  22. // PURPOSE.
  23. //---------------------------------------------------------------------
  24. using System;
  25. using System.IO;
  26. using System.Text;
  27. using System.Runtime.InteropServices;
  28. using Microsoft.Win32.SafeHandles;
  29. namespace Microsoft.Samples.BizTalk.Adapter.Tcp
  30. {
  31. /// <summary>
  32. /// Implements a virtual stream, i.e. the always seekable stream which
  33. /// uses configurable amount of memory to reduce a memory footprint and
  34. /// temporarily stores remaining data in a temporary file on disk.
  35. /// </summary>
  36. public sealed class VirtualStream : Stream, IDisposable
  37. {
  38. /// <summary>
  39. /// Memory handling.
  40. /// </summary>
  41. public enum MemoryFlag
  42. {
  43. AutoOverFlowToDisk = 0,
  44. OnlyInMemory = 1,
  45. OnlyToDisk = 2
  46. }
  47. // Constants
  48. private const int MemoryThreshold = 4*1024*1024; // The maximum possible memory consumption (4Mb)
  49. private const int DefaultMemorySize = 4*1024; // Default memory consumption (4Kb)
  50. private Stream wrappedStream;
  51. private bool isDisposed;
  52. private bool isInMemory;
  53. private int thresholdSize;
  54. private MemoryFlag memoryStatus;
  55. /// <summary>
  56. /// Initializes a VirtualStream instance with default parameters (10K memory buffer,
  57. /// allow overflow to disk).
  58. /// </summary>
  59. public VirtualStream()
  60. : this(DefaultMemorySize, MemoryFlag.AutoOverFlowToDisk, new MemoryStream())
  61. {
  62. }
  63. /// <summary>
  64. /// Initializes a VirtualStream instance with memory buffer size.
  65. /// </summary>
  66. /// <param name="bufferSize">Memory buffer size</param>
  67. public VirtualStream(int bufferSize)
  68. : this(bufferSize, MemoryFlag.AutoOverFlowToDisk, new MemoryStream(bufferSize))
  69. {
  70. }
  71. /// <summary>
  72. /// Initializes a VirtualStream instance with a default memory size and memory flag specified.
  73. /// </summary>
  74. /// <param name="flag">Memory flag</param>
  75. public VirtualStream(MemoryFlag flag)
  76. : this(DefaultMemorySize, flag,
  77. (flag == MemoryFlag.OnlyToDisk) ? CreatePersistentStream() : new MemoryStream())
  78. {
  79. }
  80. /// <summary>
  81. /// Initializes a VirtualStream instance with a memory buffer size and memory flag specified.
  82. /// </summary>
  83. /// <param name="bufferSize">Memory buffer size</param>
  84. /// <param name="flag">Memory flag</param>
  85. public VirtualStream(int bufferSize, MemoryFlag flag)
  86. : this(bufferSize, flag,
  87. (flag == MemoryFlag.OnlyToDisk) ? CreatePersistentStream() : new MemoryStream(bufferSize))
  88. {
  89. }
  90. /// <summary>
  91. /// Initializes a VirtualStream instance with a memory buffer size, memory flag and underlying stream
  92. /// specified.
  93. /// </summary>
  94. /// <param name="bufferSize">Memory buffer size</param>
  95. /// <param name="flag">Memory flag</param>
  96. /// <param name="dataStream">Underlying stream</param>
  97. private VirtualStream(int bufferSize, MemoryFlag flag, Stream dataStream)
  98. {
  99. if (null == dataStream)
  100. throw new ArgumentNullException("dataStream");
  101. isInMemory = (flag != MemoryFlag.OnlyToDisk);
  102. memoryStatus = flag;
  103. bufferSize = Math.Min(bufferSize, MemoryThreshold);
  104. thresholdSize = bufferSize;
  105. if (isInMemory)
  106. wrappedStream = dataStream; // Don't want to double wrap memory stream
  107. else
  108. wrappedStream = new BufferedStream(dataStream, bufferSize);
  109. isDisposed = false;
  110. }
  111. #region Stream Methods and Properties
  112. /// <summary>
  113. /// Gets a flag indicating whether a stream can be read.
  114. /// </summary>
  115. override public bool CanRead
  116. {
  117. get {return wrappedStream.CanRead;}
  118. }
  119. /// <summary>
  120. /// Gets a flag indicating whether a stream can be written.
  121. /// </summary>
  122. override public bool CanWrite
  123. {
  124. get {return wrappedStream.CanWrite;}
  125. }
  126. /// <summary>
  127. /// Gets a flag indicating whether a stream can seek.
  128. /// </summary>
  129. override public bool CanSeek
  130. {
  131. get {return true;}
  132. }
  133. /// <summary>
  134. /// Returns the length of the source stream.
  135. /// <seealso cref="GetLength()"/>
  136. /// </summary>
  137. override public long Length
  138. {
  139. get {return wrappedStream.Length;}
  140. }
  141. /// <summary>
  142. /// Gets or sets a position in the stream.
  143. /// </summary>
  144. override public long Position
  145. {
  146. get {return wrappedStream.Position;}
  147. set {wrappedStream.Seek(value, SeekOrigin.Begin);}
  148. }
  149. /// <summary>
  150. /// <see cref="Stream.Close()"/>
  151. /// </summary>
  152. /// <remarks>
  153. /// Calling other methods after calling Close() may result in a ObjectDisposedException beeing throwed.
  154. /// </remarks>
  155. override public void Close()
  156. {
  157. if(!isDisposed)
  158. {
  159. GC.SuppressFinalize(this);
  160. Cleanup();
  161. }
  162. }
  163. /// <summary>
  164. /// <see cref="Stream.Flush()"/>
  165. /// </summary>
  166. /// <remarks>
  167. /// </remarks>
  168. override public void Flush()
  169. {
  170. ThrowIfDisposed();
  171. wrappedStream.Flush();
  172. }
  173. /// <summary>
  174. /// <see cref="Stream.Read()"/>
  175. /// </summary>
  176. /// <param name="buffer"></param>
  177. /// <param name="offset"></param>
  178. /// <param name="count"></param>
  179. /// <returns>
  180. /// The number of bytes read
  181. /// </returns>
  182. /// <remarks>
  183. /// May throw <see cref="ObjectDisposedException"/>.
  184. /// It will read from cached persistence stream
  185. /// </remarks>
  186. override public int Read(byte[] buffer, int offset, int count)
  187. {
  188. ThrowIfDisposed();
  189. return wrappedStream.Read(buffer, offset, count);
  190. }
  191. /// <summary>
  192. /// <see cref="Stream.Seek()"/>
  193. /// </summary>
  194. /// <param name="offset"></param>
  195. /// <param name="origin"></param>
  196. /// <returns>
  197. /// The current position
  198. /// </returns>
  199. /// <remarks>
  200. /// May throw <see cref="ObjectDisposedException"/>.
  201. /// It will cache any new data into the persistence stream
  202. /// </remarks>
  203. override public long Seek(long offset, SeekOrigin origin)
  204. {
  205. ThrowIfDisposed();
  206. return wrappedStream.Seek(offset, origin);
  207. }
  208. /// <summary>
  209. /// <see cref="Stream.SetLength()"/>
  210. /// </summary>
  211. /// <param name="length"></param>
  212. /// <remarks>
  213. /// May throw <see cref="ObjectDisposedException"/>.
  214. /// </remarks>
  215. override public void SetLength(long length)
  216. {
  217. ThrowIfDisposed();
  218. // Check if new position is greater than allowed by threshold
  219. if (memoryStatus == MemoryFlag.AutoOverFlowToDisk &&
  220. isInMemory &&
  221. length > thresholdSize)
  222. {
  223. // Currently in memory, and the new write will push it over the limit
  224. // Switching to Persist Stream
  225. BufferedStream persistStream = new BufferedStream(CreatePersistentStream(), thresholdSize);
  226. // Copy current wrapped memory stream to the persist stream
  227. CopyStreamContent((MemoryStream)wrappedStream, persistStream);
  228. // Close old wrapped stream
  229. if (wrappedStream != null)
  230. wrappedStream.Close();
  231. wrappedStream = persistStream;
  232. isInMemory = false;
  233. }
  234. // Set new length for the wrapped stream
  235. wrappedStream.SetLength(length);
  236. }
  237. /// <summary>
  238. /// <see cref="Stream.Write()"/>
  239. /// <param name="buffer"></param>
  240. /// <param name="offset"></param>
  241. /// <param name="count"></param>
  242. /// </summary>
  243. /// <remarks>
  244. /// Write to the underlying stream.
  245. /// </remarks>
  246. override public void Write(byte[] buffer, int offset, int count)
  247. {
  248. ThrowIfDisposed();
  249. // Check if new position after write is greater than allowed by threshold
  250. if (memoryStatus == MemoryFlag.AutoOverFlowToDisk &&
  251. isInMemory &&
  252. (count + wrappedStream.Position) > thresholdSize)
  253. {
  254. // Currently in memory, and the new write will push it over the limit
  255. // Switching to Persist Stream
  256. BufferedStream persistStream = new BufferedStream(CreatePersistentStream(), thresholdSize);
  257. // Copy current wrapped memory stream to the persist stream
  258. CopyStreamContent((MemoryStream) wrappedStream, persistStream);
  259. // Close old wrapped stream
  260. if (wrappedStream != null)
  261. wrappedStream.Close();
  262. wrappedStream = persistStream;
  263. isInMemory = false;
  264. }
  265. wrappedStream.Write(buffer, offset, count);
  266. }
  267. #endregion
  268. #region IDisposable Interface
  269. /// <summary>
  270. /// <see cref="IDisposeable.Dispose()"/>
  271. /// </summary>
  272. /// <remarks>
  273. /// It will call <see cref="Close()"/>
  274. /// </remarks>
  275. public void Dispose()
  276. {
  277. Close();
  278. }
  279. #endregion
  280. #region Private Utility Functions
  281. /// <summary>
  282. /// Utility method called by the Finalize(), Close() or Dispose() to close and release
  283. /// both the source and the persistence stream.
  284. /// </summary>
  285. private void Cleanup()
  286. {
  287. if(!isDisposed)
  288. {
  289. isDisposed = true;
  290. if(null != wrappedStream)
  291. {
  292. wrappedStream.Close();
  293. wrappedStream = null;
  294. }
  295. }
  296. }
  297. /// <summary>
  298. /// Copies source memory stream to the target stream.
  299. /// </summary>
  300. /// <param name="source">Source memory stream</param>
  301. /// <param name="target">Target stream</param>
  302. private void CopyStreamContent(MemoryStream source, Stream target)
  303. {
  304. // Remember position for the source stream
  305. long currentPosition = source.Position;
  306. // Read and write in chunks each thresholdSize
  307. byte[] tempBuffer = new Byte[thresholdSize];
  308. int read = 0;
  309. source.Position = 0;
  310. while ((read = source.Read(tempBuffer, 0, tempBuffer.Length)) != 0)
  311. target.Write(tempBuffer, 0, read);
  312. // Set target's stream position to be the same as was in source stream. This is required because
  313. // target stream is going substitute source stream.
  314. target.Position = currentPosition;
  315. // Restore source stream's position (just in case to preserve the source stream's state)
  316. source.Position = currentPosition;
  317. }
  318. /// <summary>
  319. /// Called by other methods to check the stream state.
  320. /// It will thorw <see cref="ObjectDisposedException"/> if the stream was closed or disposed.
  321. /// </summary>
  322. private void ThrowIfDisposed()
  323. {
  324. if(isDisposed || null == wrappedStream)
  325. throw new ObjectDisposedException("VirtualStream");
  326. }
  327. /// <summary>
  328. /// Utility method.
  329. /// Creates a FileStream with a unique name and the temporary and delete-on-close attributes.
  330. /// </summary>
  331. /// <returns>
  332. /// The temporary persistence stream
  333. /// </returns>
  334. public static Stream CreatePersistentStream()
  335. {
  336. StringBuilder name = new StringBuilder(256);
  337. IntPtr handle;
  338. if(0 == GetTempFileName(Path.GetTempPath(), "BTS", 0, name))
  339. throw new IOException("GetTempFileName Failed.", Marshal.GetHRForLastWin32Error());
  340. handle = CreateFile(name.ToString(), (UInt32) FileAccess.ReadWrite, 0, IntPtr.Zero, (UInt32) FileMode.Create, 0x04000100, IntPtr.Zero);
  341. // FileStream constructor will throw exception if handle is zero or -1.
  342. return new FileStream(new SafeFileHandle(handle, true), FileAccess.ReadWrite);
  343. }
  344. [DllImport("kernel32.dll")]
  345. private static extern UInt32 GetTempFileName
  346. (
  347. string path,
  348. string prefix,
  349. UInt32 unique,
  350. StringBuilder name
  351. );
  352. [DllImport("kernel32.dll")]
  353. private static extern IntPtr CreateFile
  354. (
  355. string name,
  356. UInt32 accessMode,
  357. UInt32 shareMode,
  358. IntPtr security,
  359. UInt32 createMode,
  360. UInt32 flags,
  361. IntPtr template
  362. );
  363. #endregion
  364. }
  365. }