Back to index

lightning-sunbird  0.9+nobinonly
nsMetricsConfig.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* vim:set ts=2 sw=2 sts=2 et cindent: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the Metrics extension.
00017  *
00018  * The Initial Developer of the Original Code is Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2006
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsMetricsService.h"
00040 #include "nsStringUtils.h"
00041 #include "nsIDOMDocument.h"
00042 #include "nsIDOMParser.h"
00043 #include "nsIDOMElement.h"
00044 #include "nsIDOM3Node.h"
00045 #include "nsIFileStreams.h"
00046 #include "nsILocalFile.h"
00047 #include "nsComponentManagerUtils.h"
00048 #include "nsNetCID.h"
00049 #include "prprf.h"
00050 #include "nsTArray.h"
00051 #include "nsIDOMSerializer.h"
00052 
00053 #define NS_DEFAULT_UPLOAD_INTERVAL_SEC 60 * 5
00054 
00055 //-----------------------------------------------------------------------------
00056 
00057 static const nsString
00058 MakeKey(const nsAString &eventNS, const nsAString &eventName)
00059 {
00060   // Since eventName must be a valid XML NCName, we can use ':' to separate
00061   // eventName from eventNS when formulating our hash key.
00062   NS_ASSERTION(FindChar(eventName, ':') == -1, "Not a valid NCName");
00063 
00064   nsString key;
00065   key.Append(eventName);
00066   key.Append(':');
00067   key.Append(eventNS);
00068   return key;
00069 }
00070 
00071 static void
00072 SplitKey(const nsString &key, nsString &eventNS, nsString &eventName)
00073 {
00074   PRInt32 colon = FindChar(key, ':');
00075   if (colon == -1) {
00076     NS_ERROR("keys from MakeKey should always have a colon");
00077     return;
00078   }
00079 
00080   eventName = Substring(key, 0, colon);
00081   eventNS = Substring(key, colon + 1, key.Length() - colon - 1);
00082 }
00083 
00084 // This method leaves the result value unchanged if a parsing error occurs.
00085 static void
00086 ReadIntegerAttr(nsIDOMElement *elem, const nsAString &attrName, PRInt32 *result)
00087 {
00088   nsString attrValue;
00089   elem->GetAttribute(attrName, attrValue);
00090 
00091   NS_ConvertUTF16toUTF8 attrValueUtf8(attrValue);
00092   PR_sscanf(attrValueUtf8.get(), "%ld", result);
00093 }
00094 
00095 //-----------------------------------------------------------------------------
00096 
00097 nsMetricsConfig::nsMetricsConfig()
00098 {
00099 }
00100 
00101 PRBool
00102 nsMetricsConfig::Init()
00103 {
00104   if (!mEventSet.Init() || !mNSURIToPrefixMap.Init()) {
00105     return PR_FALSE;
00106   }
00107   Reset();
00108   return PR_TRUE;
00109 }
00110 
00111 void
00112 nsMetricsConfig::Reset()
00113 {
00114   // By default, we have no event limit, but all collectors are disabled
00115   // until we're told by the server to enable them.
00116   NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called");
00117 
00118   mEventSet.Clear();
00119   mNSURIToPrefixMap.Clear();
00120   mEventLimit = PR_INT32_MAX;
00121   mUploadInterval = NS_DEFAULT_UPLOAD_INTERVAL_SEC;
00122   mHasConfig = PR_FALSE;
00123 }
00124 
00125 nsresult
00126 nsMetricsConfig::Load(nsIFile *file)
00127 {
00128   // The given file references a XML file with the following structure:
00129   //
00130   // <response xmlns="http://www.mozilla.org/metrics">
00131   //   <config xmlns:foo="http://foo.com/metrics">
00132   //     <collectors>
00133   //       <collector type="ui"/>
00134   //       <collector type="document"/>
00135   //       <collector type="window"/>
00136   //       <collector type="foo:mystat"/>
00137   //     </collectors>
00138   //     <limit events="200"/>
00139   //     <upload interval="600"/>
00140   //   </config>
00141   // </response>
00142 
00143   NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called");
00144 
00145   PRInt64 fileSize;
00146   nsresult rv = file->GetFileSize(&fileSize);
00147   NS_ENSURE_SUCCESS(rv, rv);
00148   NS_ENSURE_STATE(fileSize <= PR_INT32_MAX);
00149 
00150   nsCOMPtr<nsIFileInputStream> stream =
00151       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID);
00152   NS_ENSURE_STATE(stream);
00153   rv = stream->Init(file, -1, -1, 0);
00154   NS_ENSURE_SUCCESS(rv, rv);
00155 
00156   nsCOMPtr<nsIDOMParser> parser = do_CreateInstance(NS_DOMPARSER_CONTRACTID);
00157   NS_ENSURE_STATE(parser);
00158 
00159   nsCOMPtr<nsIDOMDocument> doc;
00160   parser->ParseFromStream(stream, nsnull, PRInt32(fileSize), "application/xml",
00161                           getter_AddRefs(doc));
00162   NS_ENSURE_STATE(doc);
00163 
00164   // Now, walk the DOM.  Most elements are optional, but we check the root
00165   // element to make sure it's a valid response document.
00166   nsCOMPtr<nsIDOMElement> elem;
00167   doc->GetDocumentElement(getter_AddRefs(elem));
00168   NS_ENSURE_STATE(elem);
00169 
00170   nsString nameSpace;
00171   elem->GetNamespaceURI(nameSpace);
00172   if (!nameSpace.Equals(NS_LITERAL_STRING(NS_METRICS_NAMESPACE))) {
00173     // We have a root element, but it's the wrong namespace
00174     return NS_ERROR_FAILURE;
00175   }
00176 
00177   nsString tag;
00178   elem->GetLocalName(tag);
00179   if (!tag.Equals(NS_LITERAL_STRING("response"))) {
00180     // The root tag isn't what we're expecting
00181     return NS_ERROR_FAILURE;
00182   }
00183 
00184   // At this point, we can clear our old configuration.
00185   Reset();
00186 
00187   ForEachChildElement(elem, &nsMetricsConfig::ProcessToplevelElement);
00188   return NS_OK;
00189 }
00190 
00191 nsresult
00192 nsMetricsConfig::Save(nsILocalFile *file)
00193 {
00194   nsCOMPtr<nsIDOMDocument> doc =
00195     do_CreateInstance("@mozilla.org/xml/xml-document;1");
00196   NS_ENSURE_STATE(doc);
00197 
00198   nsCOMPtr<nsIDOMElement> response;
00199   nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("response"),
00200                                 getter_AddRefs(response));
00201   NS_ENSURE_STATE(response);
00202 
00203   nsCOMPtr<nsIDOMElement> config;
00204   nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("config"),
00205                                 getter_AddRefs(config));
00206   NS_ENSURE_STATE(config);
00207 
00208   nsCOMPtr<nsIDOMElement> collectors;
00209   nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("collectors"),
00210                                 getter_AddRefs(collectors));
00211   NS_ENSURE_STATE(collectors);
00212 
00213   nsTArray<nsString> events;
00214   GetEvents(events);
00215 
00216   nsCOMPtr<nsIDOMNode> nodeOut;
00217   nsresult rv;
00218 
00219   for (PRUint32 i = 0; i < events.Length(); ++i) {
00220     nsString eventNS, eventName;
00221     SplitKey(events[i], eventNS, eventName);
00222 
00223     nsString prefix;
00224     if (!eventNS.Equals(NS_LITERAL_STRING(NS_METRICS_NAMESPACE))) {
00225       if (!mNSURIToPrefixMap.Get(eventNS, &prefix)) {
00226         MS_LOG(("uri %s not in prefix map",
00227                 NS_ConvertUTF16toUTF8(eventNS).get()));
00228         continue;
00229       }
00230 
00231       // Declare the namespace prefix on the root element
00232       nsString attrName(NS_LITERAL_STRING("xmlns:"));
00233       attrName.Append(prefix);
00234       response->SetAttribute(attrName, eventNS);
00235     }
00236 
00237     nsCOMPtr<nsIDOMElement> collector;
00238     nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("collector"),
00239                                   getter_AddRefs(collector));
00240     NS_ENSURE_STATE(collector);
00241 
00242     nsString shortName;
00243     if (!prefix.IsEmpty()) {
00244       shortName = prefix;
00245       shortName.Append(':');
00246     }
00247     shortName.Append(eventName);
00248 
00249     collector->SetAttribute(NS_LITERAL_STRING("type"), eventName);
00250     collectors->AppendChild(collector, getter_AddRefs(nodeOut));
00251   }
00252   config->AppendChild(collectors, getter_AddRefs(nodeOut));
00253 
00254   if (mEventLimit != PR_INT32_MAX) {
00255     nsCOMPtr<nsIDOMElement> limit;
00256     nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("limit"),
00257                                   getter_AddRefs(limit));
00258     NS_ENSURE_STATE(limit);
00259 
00260     nsString limitStr;
00261     AppendInt(limitStr, mEventLimit);
00262     limit->SetAttribute(NS_LITERAL_STRING("events"), limitStr);
00263     config->AppendChild(limit, getter_AddRefs(nodeOut));
00264   }
00265 
00266   nsCOMPtr<nsIDOMElement> upload;
00267   nsMetricsUtils::CreateElement(doc, NS_LITERAL_STRING("upload"),
00268                                 getter_AddRefs(upload));
00269   NS_ENSURE_STATE(upload);
00270 
00271   nsString intervalStr;
00272   AppendInt(intervalStr, mUploadInterval);
00273   upload->SetAttribute(NS_LITERAL_STRING("interval"), intervalStr);
00274   config->AppendChild(upload, getter_AddRefs(nodeOut));
00275 
00276   response->AppendChild(config, getter_AddRefs(nodeOut));
00277 
00278   nsCOMPtr<nsIDOMSerializer> ds =
00279     do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID);
00280   NS_ENSURE_STATE(ds);
00281 
00282   nsString docText;
00283   rv = ds->SerializeToString(response, docText);
00284   NS_ENSURE_SUCCESS(rv, rv);
00285 
00286   NS_ConvertUTF16toUTF8 utf8Doc(docText);
00287   PRInt32 num = utf8Doc.Length();
00288 
00289   PRFileDesc *fd;
00290   rv = file->OpenNSPRFileDesc(
00291       PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd);
00292   NS_ENSURE_SUCCESS(rv, rv);
00293 
00294   PRBool success = (PR_Write(fd, utf8Doc.get(), num) == num);
00295   PR_Close(fd);
00296 
00297   return success ? NS_OK : NS_ERROR_FAILURE;
00298 }
00299 
00300 void
00301 nsMetricsConfig::ForEachChildElement(nsIDOMElement *elem,
00302                                      ForEachChildElementCallback callback)
00303 {
00304   nsCOMPtr<nsIDOMNode> node, next;
00305   elem->GetFirstChild(getter_AddRefs(node));
00306   while (node) {
00307     nsCOMPtr<nsIDOMElement> childElem = do_QueryInterface(node);
00308     if (childElem) {
00309       // Skip elements that are not in our namespace
00310       nsString namespaceURI;
00311       childElem->GetNamespaceURI(namespaceURI);
00312       if (namespaceURI.Equals(NS_LITERAL_STRING(NS_METRICS_NAMESPACE)))
00313         (this->*callback)(childElem);
00314     }
00315     node->GetNextSibling(getter_AddRefs(next));
00316     node.swap(next);
00317   }
00318 }
00319 
00320 void
00321 nsMetricsConfig::ProcessToplevelElement(nsIDOMElement *elem)
00322 {
00323   // Process a top-level element
00324 
00325   nsString name;
00326   elem->GetLocalName(name);
00327   if (name.Equals(NS_LITERAL_STRING("config"))) {
00328     mHasConfig = PR_TRUE;
00329     ForEachChildElement(elem, &nsMetricsConfig::ProcessConfigChild);
00330   }
00331 }
00332 
00333 void
00334 nsMetricsConfig::ProcessConfigChild(nsIDOMElement *elem)
00335 {
00336   // Process a config element child
00337 
00338   nsString name;
00339   elem->GetLocalName(name);
00340   if (name.Equals(NS_LITERAL_STRING("collectors"))) {
00341     // Enumerate <collector> elements
00342     ForEachChildElement(elem, &nsMetricsConfig::ProcessCollectorElement);
00343   } else if (name.Equals(NS_LITERAL_STRING("limit"))) {
00344     ReadIntegerAttr(elem, NS_LITERAL_STRING("events"), &mEventLimit);
00345   } else if (name.Equals(NS_LITERAL_STRING("upload"))) {
00346     ReadIntegerAttr(elem, NS_LITERAL_STRING("interval"), &mUploadInterval);
00347   }
00348 }
00349 
00350 void
00351 nsMetricsConfig::ProcessCollectorElement(nsIDOMElement *elem)
00352 {
00353   // Make sure we are dealing with a <collector> element.
00354   nsString localName;
00355   elem->GetLocalName(localName);
00356   if (!localName.Equals(NS_LITERAL_STRING("collector")))
00357     return;
00358 
00359   nsString type;
00360   elem->GetAttribute(NS_LITERAL_STRING("type"), type);
00361   if (type.IsEmpty())
00362     return;
00363 
00364   // Get the namespace URI specified by any prefix of |type|.
00365   nsCOMPtr<nsIDOM3Node> node = do_QueryInterface(elem);
00366   if (!node)
00367     return;
00368 
00369   // Check to see if this type references a specific namespace.
00370   PRInt32 colon = FindChar(type, ':');
00371 
00372   nsString namespaceURI;
00373   if (colon == -1) {
00374     node->LookupNamespaceURI(EmptyString(), namespaceURI);
00375     // value is the EventName
00376   } else {
00377     // value is NamespacePrefix + ":" + EventName
00378     nsString prefix(StringHead(type, colon));
00379     node->LookupNamespaceURI(prefix, namespaceURI);
00380     type.Cut(0, colon + 1);
00381 
00382     // Add this namespace -> prefix mapping to our lookup table
00383     mNSURIToPrefixMap.Put(namespaceURI, prefix);
00384   }
00385 
00386   mEventSet.PutEntry(MakeKey(namespaceURI, type));
00387 }
00388 
00389 PRBool
00390 nsMetricsConfig::IsEventEnabled(const nsAString &eventNS,
00391                                 const nsAString &eventName) const
00392 {
00393   NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called");
00394   return mEventSet.GetEntry(MakeKey(eventNS, eventName)) != nsnull;
00395 }
00396 
00397 void
00398 nsMetricsConfig::SetEventEnabled(const nsAString &eventNS,
00399                                  const nsAString &eventName, PRBool enabled)
00400 {
00401   NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called");
00402   nsString key = MakeKey(eventNS, eventName);
00403   if (enabled) {
00404     mEventSet.PutEntry(key);
00405   } else {
00406     mEventSet.RemoveEntry(key);
00407   }
00408 }
00409 
00410 void
00411 nsMetricsConfig::ClearEvents()
00412 {
00413   NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called");
00414   mEventSet.Clear();
00415 }
00416 
00417 /* static */ PLDHashOperator PR_CALLBACK
00418 nsMetricsConfig::CopyKey(nsStringHashKey *entry, void *userData)
00419 {
00420   NS_STATIC_CAST(nsTArray<nsString> *, userData)->
00421     AppendElement(entry->GetKey());
00422   return PL_DHASH_NEXT;
00423 }
00424 
00425 void
00426 nsMetricsConfig::GetEvents(nsTArray<nsString> &events) {
00427   mEventSet.EnumerateEntries(CopyKey, &events);
00428 }