/// <summary> /// Stores data to cache. /// /// If data does not already exist for this key on the server, or if the key is being<br/> /// deleted, the specified value will not be stored.<br/> /// The server will automatically delete the value when the expiration time has been reached.<br/> /// <br/> /// If compression is enabled, and the data is longer than the compression threshold<br/> /// the data will be stored in compressed form.<br/> /// <br/> /// As of the current release, all objects stored will use serialization. /// </summary> /// /// <param name="cmdType">action to take (set, add, replace)</param> /// <param name="key">key to store cache under</param> /// <param name="value">object to cache</param> /// <param name="expiry">expiration, if any</param> /// <param name="sock">SockIO to use</param> /// /// <returns><code>true</code> if successful, <code>fale</code> or exception otherwise</returns> private bool SetItem(SetCommandType cmdType, string key, object value, DateTime expiry, SockIOPool.SockIO sock) { // store flags int flags = 0; // byte array to hold data byte[] val; if(expiry.Equals(DateTime.MinValue)) expiry = new DateTime(0); // serialize the object // unless client request data to be not serialized // in which case, we will go with string representation // but only for a select few classes if (!(value is string || value is Int32 || value is Double || value is Single || value is Int64 || value is SByte || value is Int16 || value is Char || value is StringBuilder)) { // we can ONLY not serialize the above classes // this is generally a bad idea as we should always // just use serialization. // but, it is useful for sharing data between non-.NET // and also for storing ints for the increment method if(log.IsInfoEnabled) log.Info("storing data as a string for key: " + key + " for class: " + value.GetType().FullName); val = UTF8Encoding.UTF8.GetBytes(value.ToString()); } else { if(log.IsInfoEnabled) log.Info("serializing for key: " + key + " for class: " + value.GetType().FullName); try { MemoryStream bos = new MemoryStream(); new BinaryFormatter().Serialize((new BinaryWriter(bos)).BaseStream, value); val = bos.ToArray(); flags |= F_SERIALIZED; } catch(SerializationException se) { if(log.IsErrorEnabled) log.Error("failed to serialize obj: " + value.ToString(), se); // return socket to pool and bail sock.Close(); #if EXCEPTIONS throw new MemcachedException(se); #else return false; #endif } } // now try to compress if we want to // and if the length is over the threshold if (compressEnable && val.Length > compressThreshold) { if(log.IsInfoEnabled) log.Info("trying to compress data, size prior to compression: " + val.Length); try { MemoryStream bos = new MemoryStream(val.Length); GZipOutputStream gos = new GZipOutputStream(bos); gos.Write(val, 0, val.Length); gos.Close(); // store it and set compression flag val = bos.ToArray(); flags |= F_COMPRESSED; if(log.IsInfoEnabled) log.Info("compression succeeded, size after: " + val.Length); } // TODO Not sure which specific exception(s) is/are thrown catch(Exception e) { if(log.IsErrorEnabled) log.Error("Exception while compressing stream", e); } } // now write the data to the cache server StringBuilder cmd = new StringBuilder(); switch(cmdType) { case SetCommandType.Add: cmd.Append("add"); break; case SetCommandType.Replace: cmd.Append("replace"); break; case SetCommandType.Set: cmd.Append("set"); break; default: // As if this can actually happen throw new ArgumentException("cmdType"); } cmd.Append(" "); cmd.Append(key); cmd.Append(" "); cmd.Append(flags.ToString()); cmd.Append(" "); cmd.Append(expiry.Ticks / 1000); cmd.Append(" "); cmd.Append(val.Length.ToString()); cmd.Append("\n"); try { sock.Write(UTF8Encoding.UTF8.GetBytes(cmd.ToString())); sock.Write(val); sock.Write(UTF8Encoding.UTF8.GetBytes("\n")); sock.Flush(); // get result code string line = sock.ReadLine(); if(log.IsInfoEnabled) log.Info("memcache cmd (result code): " + cmd + " (" + line + ")"); switch(line) { case STORED: if(log.IsInfoEnabled) log.Info("data successfully stored for key: " + key); return true; case NOTSTORED: if(log.IsInfoEnabled) log.Info("data not stored in cache for key: " + key); #if EXCEPTIONS throw new NotStoredException(key); #else return false; #endif default: if(log.IsErrorEnabled) log.Error("error storing data in cache for key: " + key + " -- length: " + val.Length); #if EXCEPTIONS throw new MemcachedException(line); #else return false; #endif } } catch(IOException ioe) { if(log.IsErrorEnabled) log.Error("exception thrown while writing bytes to server on set", ioe); try { sock.TrueClose(); } catch(IOException ioee) { if(log.IsErrorEnabled) log.Error("failed to close socket : " + sock.ToString(), ioee); } sock = null; #if EXCEPTIONS throw new SocketException(e); #else return false; #endif } finally { if(sock != null) sock.Close(); } }
/// <summary> /// Increments/decrements the value at the specified key by inc. /// /// From Javadocs: /// Note that the server uses a 32-bit unsigned integer, and checks for<br/> /// underflow. In the event of underflow, the result will be zero. Because<br/> /// java lacks unsigned types, the value is returned as a 64-bit integer.<br/> /// The server will only decrement a value if it already exists;<br/> /// if a value is not found, -1 will be returned. /// /// .NET supports unsigned types so refactor /// </summary> /// /// <param name="cmdType">increment/decrement type</param> /// <param name="key">cache key</param> /// <param name="qty">amount to incr or decr</param> /// <param name="sock">SockIO to use</param> /// /// <returns> new value or -1 if not exist</returns> private long IncrDecrItem(IncrDecrCommandType cmdType, string key, long qty, SockIOPool.SockIO sock) { // Assumes non-null arguments if(log.IsDebugEnabled) log.Debug("memcache incr/decr command: " + cmdType.ToString()); // now write the data to the cache server StringBuilder cmd = new StringBuilder(); switch(cmdType) { case IncrDecrCommandType.Increment: cmd.Append("incr"); break; case IncrDecrCommandType.Decrement: cmd.Append("decr"); break; default: // As if this can actually happen throw new ArgumentException("cmdType"); } cmd.Append(" "); cmd.Append(key); cmd.Append(" "); cmd.Append(qty.ToString()); cmd.Append("\n"); try { sock.Write(UTF8Encoding.UTF8.GetBytes(cmd.ToString())); sock.Flush(); // get result back string line = sock.ReadLine(); if(Regex.IsMatch("[^0-9-]", line)) { return long.Parse(line); } else if(NOTFOUND.Equals(line)) { if(log.IsInfoEnabled) log.Info("key not found to incr/decr for key: " + key); #if EXCEPTIONS throw new NotFoundException(); #else // TODO Hacky return -1; #endif } if(log.IsErrorEnabled) log.Error("error incr/decr key: " + key); #if EXCEPTIONS throw new MemcachedException(cmd.ToString()); #else // TODO Hacky return -1; #endif } catch(IOException ioe) { // exception thrown if(log.IsErrorEnabled) log.Error("exception thrown while trying to incr/decr", ioe); try { sock.TrueClose(); } catch(IOException ioee) { if(log.IsErrorEnabled) log.Error("failed to close socket : " + sock.ToString(), ioee); } sock = null; #if EXCEPTIONS throw new SocketException(e); #else return -1; #endif } finally { if(sock != null) sock.Close(); } }
/// <summary> This method loads the data from cache into a Map. /// /// Pass a SockIO object which is ready to receive data and a HashMap<br/> /// to store the results. /// /// </summary> /// <param name="sock">socket waiting to pass back data /// </param> /// <param name="hm">hashmap to store data into /// </param> /// <throws> IOException if io exception happens while reading from socket </throws> private void LoadItems(SockIOPool.SockIO sock, IDictionary hm) { while (true) { string line = sock.ReadLine(); if(log.IsDebugEnabled) log.Debug("line: " + line); if (line.StartsWith(VALUE)) { string[] info = line.Split(' '); string key = info[1]; int flag = int.Parse(info[2]); int length = int.Parse(info[3]); if(log.IsDebugEnabled) log.Debug("key: " + key + "\n" + "flags: " + flag + "\n" + "length: " + length); // read obj into buffer byte[] buf = new byte[length]; sock.Read(buf); sock.ClearEOL(); // ready object object o; // check for compression if ((flag & F_COMPRESSED) != 0) { try { // read the input stream, and write to a byte array output stream since // we have to read into a byte array, but we don't know how large it // will need to be, and we don't want to resize it a bunch GZipInputStream gzi = new GZipInputStream(new MemoryStream(buf)); MemoryStream bos = new MemoryStream(buf.Length); int count; byte[] tmp = new byte[2048]; while ((count = gzi.Read(tmp, 0, tmp.Length)) != - 1) { bos.Write(tmp, 0, count); } // store uncompressed back to buffer buf = bos.ToArray(); gzi.Close(); } catch(IOException ioe) { if(log.IsErrorEnabled) log.Error("IOException thrown while trying to uncompress input stream for key: " + key, ioe); throw new IOException("IOException thrown while trying to uncompress input stream for key: " + key); } } // we can only take out serialized objects if ((flag & F_SERIALIZED) == 0) { if(log.IsInfoEnabled) log.Info("this object is not a serialized object. Stuffing into a string."); o = new string(UTF8Encoding.UTF8.GetChars(buf)); } else { // deserialize if the data is serialized BinaryReader ois = new BinaryReader(new MemoryStream(buf)); try { o = new BinaryFormatter().Deserialize(ois.BaseStream); if(log.IsInfoEnabled) log.Info("deserializing " + o.GetType().FullName); } catch(Exception e) { if(log.IsErrorEnabled) log.Error("ClassNotFoundException thrown while trying to deserialize for key: " + key, e); throw new IOException("failed while trying to deserialize for key: " + key); } } // store the object into the cache hm.Add(key, o); } else if (END.Equals(line)) { if(log.IsDebugEnabled) log.Debug("finished reading from cache server"); break; } } }
/// <summary> /// Delete item /// </summary> /// <param name="key">Key to delete</param> /// <param name="expiry">Expiration to use</param> /// <param name="sock">SockIO to use</param> /// <returns><code>true</code> if successful, <code>fale</code> or exception otherwise</returns> private static bool DeleteItem(string key, DateTime expiry, SockIOPool.SockIO sock) { // Assumes non-null arguments // build command StringBuilder command = new StringBuilder(); command.Append("delete "); command.Append(key); if(!expiry.Equals(DateTime.MinValue)) { command.Append(" "); command.Append(expiry.Ticks / 1000); } command.Append("\n"); try { sock.Write(UTF8Encoding.UTF8.GetBytes(command.ToString())); sock.Flush(); // if we get appropriate response back, then we return true string line = sock.ReadLine(); switch(line) { case DELETED: return true; case NOTFOUND: #if EXCEPTIONS throw new NotFoundException(); #else return false; #endif default: #if EXCEPTIONS throw new MemcachedException(line); #else return false; #endif } } catch(IOException ioe) { if(log.IsErrorEnabled) log.Error("exception thrown while writing bytes to server on delete", ioe); try { sock.TrueClose(); } catch(IOException ioee) { if(log.IsErrorEnabled) log.Error("failed to close socket : " + sock.ToString(), ioee); } sock = null; #if EXCEPTIONS throw new SocketException(e); #else return false; #endif } finally { if(sock != null) sock.Close(); } }