(file) Return to CIMObjectPath.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Common

   1 karl  1.45 //%2005////////////////////////////////////////////////////////////////////////
   2 chip  1.1  //
   3 karl  1.43 // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development
   4            // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems.
   5            // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L.P.;
   6 karl  1.30 // IBM Corp.; EMC Corporation, The Open Group.
   7 karl  1.43 // Copyright (c) 2004 BMC Software; Hewlett-Packard Development Company, L.P.;
   8            // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group.
   9 karl  1.45 // Copyright (c) 2005 Hewlett-Packard Development Company, L.P.; IBM Corp.;
  10            // EMC Corporation; VERITAS Software Corporation; The Open Group.
  11 chip  1.1  //
  12            // Permission is hereby granted, free of charge, to any person obtaining a copy
  13            // of this software and associated documentation files (the "Software"), to
  14            // deal in the Software without restriction, including without limitation the
  15            // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  16            // sell copies of the Software, and to permit persons to whom the Software is
  17            // furnished to do so, subject to the following conditions:
  18 chip  1.47 //
  19 chip  1.1  // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
  20            // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
  21            // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
  22            // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  23            // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  24            // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  25            // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26            // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27            //
  28            //==============================================================================
  29            //
  30 kumpf 1.2  // Author: Mike Brasher (mbrasher@bmc.com)
  31 chip  1.1  //
  32 kumpf 1.2  // Modified By: Roger Kumpf, Hewlett-Packard Company (roger_kumpf@hp.com)
  33 kumpf 1.9  //              Carol Ann Krug Graves, Hewlett-Packard Company
  34            //                (carolann_graves@hp.com)
  35 dave.sudlik 1.44 //              Dave Sudlik, IBM (dsudlik@us.ibm.com)
  36 chip        1.1  //
  37                  //%/////////////////////////////////////////////////////////////////////////////
  38                  
  39 kumpf       1.2  #include <Pegasus/Common/Config.h>
  40                  #include <cstring>
  41 kumpf       1.22 #include <iostream>
  42 kumpf       1.2  #include "HashTable.h"
  43                  #include "CIMObjectPath.h"
  44                  #include "Indentor.h"
  45                  #include "CIMName.h"
  46 a.arora     1.39 #include "Destroyer.h"
  47 kumpf       1.2  #include "XmlWriter.h"
  48                  #include "XmlReader.h"
  49 kumpf       1.11 #include "ArrayInternal.h"
  50 chip        1.1  
  51                  PEGASUS_NAMESPACE_BEGIN
  52 kumpf       1.2  
  53 kumpf       1.16 #define PEGASUS_ARRAY_T CIMKeyBinding
  54 kumpf       1.2  # include "ArrayImpl.h"
  55                  #undef PEGASUS_ARRAY_T
  56                  
  57                  #define PEGASUS_ARRAY_T CIMObjectPath
  58                  # include "ArrayImpl.h"
  59                  #undef PEGASUS_ARRAY_T
  60                  
  61                  // ATTN: KS May 2002 P0 Add resolve method to CIMObjectPath.
  62                  // Add a resolve method to this class to verify that the
  63                  // reference is correct (that the class name corresponds to a real
  64                  // class and that the property names are really keys and that all keys
  65                  // of the class or used. Also be sure that there is a valid conversion
  66                  // between the string value and the value of that property).
  67                  //
  68                  // ATTN: also check to see that the reference refers to a class that is the
  69                  // same or derived from the _className member.
  70                  
  71                  ////////////////////////////////////////////////////////////////////////////////
  72                  //
  73                  // Local routines:
  74                  //
  75 kumpf       1.2  ////////////////////////////////////////////////////////////////////////////////
  76                  
  77                  static String _escapeSpecialCharacters(const String& str)
  78                  {
  79                      String result;
  80                  
  81                      for (Uint32 i = 0, n = str.size(); i < n; i++)
  82                      {
  83                          switch (str[i])
  84                          {
  85 kumpf       1.28             case '\\':
  86                                  result.append("\\\\");
  87 kumpf       1.2                  break;
  88                  
  89                              case '"':
  90 kumpf       1.13                 result.append("\\\"");
  91 kumpf       1.2                  break;
  92                  
  93                              default:
  94 kumpf       1.13                 result.append(str[i]);
  95 kumpf       1.2          }
  96                      }
  97                  
  98                      return result;
  99                  }
 100                  
 101 kumpf       1.16 static void _BubbleSort(Array<CIMKeyBinding>& x)
 102 kumpf       1.2  {
 103                      Uint32 n = x.size();
 104                  
 105 kumpf       1.24     //
 106 chip        1.47     //  If the key is a reference, the keys in the reference must also be
 107 kumpf       1.24     //  sorted
 108                      //
 109                      for (Uint32 k = 0; k < n ; k++)
 110                          if (x[k].getType () == CIMKeyBinding::REFERENCE)
 111                          {
 112                              CIMObjectPath tmp (x[k].getValue ());
 113                              Array <CIMKeyBinding> keyBindings = tmp.getKeyBindings ();
 114                              _BubbleSort (keyBindings);
 115                              tmp.setKeyBindings (keyBindings);
 116                              x[k].setValue (tmp.toString ());
 117                          }
 118                  
 119 kumpf       1.2      if (n < 2)
 120                          return;
 121                  
 122                      for (Uint32 i = 0; i < n - 1; i++)
 123                      {
 124                          for (Uint32 j = 0; j < n - 1; j++)
 125                          {
 126 chip        1.47             if (String::compareNoCase(x[j].getName().getString(),
 127 kumpf       1.17                                       x[j+1].getName().getString()) > 0)
 128 kumpf       1.2              {
 129 kumpf       1.16                 CIMKeyBinding t = x[j];
 130 kumpf       1.2                  x[j] = x[j+1];
 131                                  x[j+1] = t;
 132                              }
 133                          }
 134                      }
 135                  }
 136                  
 137                  ////////////////////////////////////////////////////////////////////////////////
 138                  //
 139 kumpf       1.16 // CIMKeyBinding
 140 kumpf       1.2  //
 141                  ////////////////////////////////////////////////////////////////////////////////
 142                  
 143 kumpf       1.16 class CIMKeyBindingRep
 144 kumpf       1.2  {
 145                  public:
 146 kumpf       1.16     CIMKeyBindingRep()
 147 kumpf       1.2      {
 148                      }
 149                  
 150 kumpf       1.16     CIMKeyBindingRep(const CIMKeyBindingRep& x)
 151 kumpf       1.2          : _name(x._name), _value(x._value), _type(x._type)
 152                      {
 153                      }
 154                  
 155 kumpf       1.16     CIMKeyBindingRep(
 156 kumpf       1.7          const CIMName& name,
 157 kumpf       1.2          const String& value,
 158 kumpf       1.16         CIMKeyBinding::Type type)
 159 kumpf       1.2          : _name(name), _value(value), _type(type)
 160                      {
 161                      }
 162                  
 163 kumpf       1.16     ~CIMKeyBindingRep()
 164 kumpf       1.2      {
 165                      }
 166                  
 167 kumpf       1.16     CIMKeyBindingRep& operator=(const CIMKeyBindingRep& x)
 168 kumpf       1.2      {
 169                          if (&x != this)
 170                          {
 171                              _name = x._name;
 172                              _value = x._value;
 173                              _type = x._type;
 174                          }
 175                          return *this;
 176                      }
 177                  
 178 kumpf       1.7      CIMName _name;
 179 kumpf       1.2      String _value;
 180 kumpf       1.16     CIMKeyBinding::Type _type;
 181 kumpf       1.2  };
 182                  
 183                  
 184 a.arora     1.39 CIMKeyBinding::CIMKeyBinding()
 185 kumpf       1.2  {
 186 a.arora     1.39     _rep = new CIMKeyBindingRep();
 187 kumpf       1.2  }
 188                  
 189 a.arora     1.39 CIMKeyBinding::CIMKeyBinding(const CIMKeyBinding& x)
 190 kumpf       1.2  {
 191 a.arora     1.39     _rep = new CIMKeyBindingRep(*x._rep);
 192 kumpf       1.2  }
 193                  
 194 a.arora     1.39 CIMKeyBinding::CIMKeyBinding(const CIMName& name, const String& value, Type type)
 195 kumpf       1.2  {
 196 a.arora     1.39     _rep = new CIMKeyBindingRep(name, value, type);
 197 kumpf       1.2  }
 198                  
 199 kumpf       1.18 CIMKeyBinding::CIMKeyBinding(const CIMName& name, const CIMValue& value)
 200                  {
 201 kumpf       1.21     if (value.isArray())
 202 kumpf       1.20     {
 203                          throw TypeMismatchException();
 204                      }
 205                  
 206 kumpf       1.18     String kbValue = value.toString();
 207                      Type kbType;
 208                  
 209                      switch (value.getType())
 210                      {
 211                      case CIMTYPE_BOOLEAN:
 212                          kbType = BOOLEAN;
 213                          break;
 214                      case CIMTYPE_CHAR16:
 215                      case CIMTYPE_STRING:
 216                      case CIMTYPE_DATETIME:
 217                          kbType = STRING;
 218                          break;
 219                      case CIMTYPE_REFERENCE:
 220                          kbType = REFERENCE;
 221                          break;
 222 dave.sudlik 1.46 //  case CIMTYPE_REAL32:
 223                  //  case CIMTYPE_REAL64:
 224 dave.sudlik 1.44     case CIMTYPE_OBJECT:
 225 dave.sudlik 1.46         // From PEP 194: EmbeddedObjects cannot be keys.
 226 dave.sudlik 1.44         throw TypeMismatchException();
 227                          break;
 228 kumpf       1.18     default:
 229                          kbType = NUMERIC;
 230                          break;
 231                      }
 232                  
 233 a.arora     1.39     _rep = new CIMKeyBindingRep(name, kbValue, kbType);
 234 kumpf       1.18 }
 235                  
 236 kumpf       1.16 CIMKeyBinding::~CIMKeyBinding()
 237 kumpf       1.2  {
 238 a.arora     1.39     delete _rep;
 239 kumpf       1.2  }
 240                  
 241 kumpf       1.16 CIMKeyBinding& CIMKeyBinding::operator=(const CIMKeyBinding& x)
 242 kumpf       1.2  {
 243 a.arora     1.39     *_rep = *x._rep;
 244 kumpf       1.2      return *this;
 245                  }
 246                  
 247 kumpf       1.16 const CIMName& CIMKeyBinding::getName() const
 248 kumpf       1.2  {
 249                      return _rep->_name;
 250                  }
 251                  
 252 kumpf       1.16 void CIMKeyBinding::setName(const CIMName& name)
 253 kumpf       1.2  {
 254                      _rep->_name = name;
 255                  }
 256                  
 257 kumpf       1.16 const String& CIMKeyBinding::getValue() const
 258 kumpf       1.2  {
 259                      return _rep->_value;
 260                  }
 261                  
 262 kumpf       1.16 void CIMKeyBinding::setValue(const String& value)
 263 kumpf       1.2  {
 264                      _rep->_value = value;
 265                  }
 266                  
 267 kumpf       1.16 CIMKeyBinding::Type CIMKeyBinding::getType() const
 268 kumpf       1.2  {
 269                      return _rep->_type;
 270                  }
 271                  
 272 kumpf       1.16 void CIMKeyBinding::setType(CIMKeyBinding::Type type)
 273 kumpf       1.2  {
 274                      _rep->_type = type;
 275                  }
 276 kumpf       1.18 
 277                  Boolean CIMKeyBinding::equal(CIMValue value)
 278                  {
 279 kumpf       1.21     if (value.isArray())
 280 kumpf       1.20     {
 281                          return false;
 282                      }
 283                  
 284 kumpf       1.18     CIMValue kbValue;
 285                  
 286                      try
 287                      {
 288                          switch (value.getType())
 289                          {
 290                          case CIMTYPE_CHAR16:
 291 kumpf       1.20             if (getType() != STRING) return false;
 292 kumpf       1.18             kbValue.set(getValue()[0]);
 293                              break;
 294                          case CIMTYPE_DATETIME:
 295 kumpf       1.20             if (getType() != STRING) return false;
 296 kumpf       1.18             kbValue.set(CIMDateTime(getValue()));
 297                              break;
 298                          case CIMTYPE_STRING:
 299 kumpf       1.20             if (getType() != STRING) return false;
 300 kumpf       1.18             kbValue.set(getValue());
 301                              break;
 302                          case CIMTYPE_REFERENCE:
 303 kumpf       1.20             if (getType() != REFERENCE) return false;
 304 kumpf       1.18             kbValue.set(CIMObjectPath(getValue()));
 305                              break;
 306 kumpf       1.20         case CIMTYPE_BOOLEAN:
 307                              if (getType() != BOOLEAN) return false;
 308                              kbValue = XmlReader::stringToValue(0, getValue().getCString(),
 309                                                                 value.getType());
 310                              break;
 311 dave.sudlik 1.46 //      case CIMTYPE_REAL32:
 312                  //      case CIMTYPE_REAL64:
 313 dave.sudlik 1.44         case CIMTYPE_OBJECT:
 314 dave.sudlik 1.46             // From PEP 194: EmbeddedObjects cannot be keys.
 315 dave.sudlik 1.44             return false;
 316                              break;
 317 kumpf       1.20         default:  // Numerics
 318                              if (getType() != NUMERIC) return false;
 319 kumpf       1.18             kbValue = XmlReader::stringToValue(0, getValue().getCString(),
 320                                                                 value.getType());
 321                              break;
 322                          }
 323                      }
 324 kumpf       1.19     catch (Exception&)
 325 kumpf       1.18     {
 326                          return false;
 327                      }
 328                  
 329                      return value.equal(kbValue);
 330                  }
 331 kumpf       1.2  
 332 kumpf       1.16 Boolean operator==(const CIMKeyBinding& x, const CIMKeyBinding& y)
 333 kumpf       1.2  {
 334 kumpf       1.33     // Check that the names and types match
 335                      if (!(x.getName().equal(y.getName())) ||
 336                          !(x.getType() == y.getType()))
 337                      {
 338                          return false;
 339                      }
 340                  
 341                      switch (x.getType())
 342                      {
 343                      case CIMKeyBinding::REFERENCE:
 344                          try
 345                          {
 346                              // References should be compared as CIMObjectPaths
 347                              return (CIMObjectPath(x.getValue()) == CIMObjectPath(y.getValue()));
 348                          }
 349                          catch (Exception&)
 350                          {
 351                              // If CIMObjectPath parsing fails, just compare strings
 352                              return (String::equal(x.getValue(), y.getValue()));
 353                          }
 354                          break;
 355 kumpf       1.33     case CIMKeyBinding::BOOLEAN:
 356                          // Case-insensitive comparison is sufficient for booleans
 357                          return (String::equalNoCase(x.getValue(), y.getValue()));
 358                          break;
 359                      case CIMKeyBinding::NUMERIC:
 360                          // Note: This comparison assumes XML syntax for integers
 361                          // First try comparing as unsigned integers
 362                          {
 363                              Uint64 xValue;
 364                              Uint64 yValue;
 365                              if (XmlReader::stringToUnsignedInteger(
 366                                      x.getValue().getCString(), xValue) &&
 367                                  XmlReader::stringToUnsignedInteger(
 368                                      y.getValue().getCString(), yValue))
 369                              {
 370                                  return (xValue == yValue);
 371                              }
 372                          }
 373                          // Next try comparing as signed integers
 374                          {
 375                              Sint64 xValue;
 376 kumpf       1.33             Sint64 yValue;
 377                              if (XmlReader::stringToSignedInteger(
 378                                      x.getValue().getCString(), xValue) &&
 379                                  XmlReader::stringToSignedInteger(
 380                                      y.getValue().getCString(), yValue))
 381                              {
 382                                  return (xValue == yValue);
 383                              }
 384                          }
 385                          // Note: Keys may not be real values, so don't try comparing as reals
 386                          // We couldn't parse the numbers, so just compare the strings
 387                          return (String::equal(x.getValue(), y.getValue()));
 388                          break;
 389                      default:  // CIMKeyBinding::STRING
 390                          return (String::equal(x.getValue(), y.getValue()));
 391                          break;
 392                      }
 393                  
 394                      PEGASUS_UNREACHABLE(return false;)
 395 kumpf       1.2  }
 396                  
 397                  
 398                  ////////////////////////////////////////////////////////////////////////////////
 399                  //
 400                  // CIMObjectPath
 401                  //
 402                  ////////////////////////////////////////////////////////////////////////////////
 403                  
 404                  class CIMObjectPathRep
 405                  {
 406                  public:
 407                      CIMObjectPathRep()
 408                      {
 409                      }
 410                  
 411                      CIMObjectPathRep(const CIMObjectPathRep& x)
 412                          : _host(x._host), _nameSpace(x._nameSpace),
 413                          _className(x._className), _keyBindings(x._keyBindings)
 414                      {
 415                      }
 416 kumpf       1.2  
 417                      CIMObjectPathRep(
 418                          const String& host,
 419 kumpf       1.7          const CIMNamespaceName& nameSpace,
 420                          const CIMName& className,
 421 kumpf       1.16         const Array<CIMKeyBinding>& keyBindings)
 422 kumpf       1.2          : _host(host), _nameSpace(nameSpace),
 423                          _className(className), _keyBindings(keyBindings)
 424                      {
 425                      }
 426                  
 427                      ~CIMObjectPathRep()
 428                      {
 429                      }
 430                  
 431                      CIMObjectPathRep& operator=(const CIMObjectPathRep& x)
 432                      {
 433                          if (&x != this)
 434                          {
 435                              _host = x._host;
 436                              _nameSpace = x._nameSpace;
 437                              _className = x._className;
 438                              _keyBindings = x._keyBindings;
 439                          }
 440                          return *this;
 441                      }
 442                  
 443 kumpf       1.34     static Boolean isValidHostname(const String& hostname)
 444                      {
 445                          //------------------------------------------------------------------
 446                          // Validate the hostname.  The hostname value may or may not be a
 447                          // fully-qualified domain name (e.g., xyz.company.com) or may be an
 448                          // IP address.  A port number may follow the hostname.
 449                          // Hostnames must match one of the following regular expressions:
 450 dave.sudlik 1.42         // ^([A-Za-z0-9][A-Za-z0-9-]*)(\.[A-Za-z][A-Za-z0-9-]*)*(:[0-9]*)?$
 451 kumpf       1.34         // ^([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)(:[0-9]*)?$
 452 dave.sudlik 1.42         // Note for Bug#1462. Be careful here, from RFC 1123:
 453 chip        1.47         // - The syntax of a legal Internet host name was specified in
 454                          //   RFC-952 [DNS:4]. One aspect of host name syntax is hereby
 455                          //   changed: the restriction on the first character is relaxed to
 456                          //   allow either a letter or a digit.
 457 dave.sudlik 1.42         // - If a dotted-decimal number can be entered without identifying
 458                          //   delimiters, then a full syntactic check must be made, because
 459                          //   a segment of a host domain name is now allowed to begin with a
 460                          //   digit and could legally be entirely numeric (see Section 6.1.2.4).
 461                          //   However, a valid host name can never have the dotted-decimal form
 462                          //   #.#.#.#, since at least the highest-level component label will be
 463 chip        1.47         //   alphabetic.
 464 dave.sudlik 1.42         // The algorithm below has been updated accordingly.
 465 kumpf       1.34         //------------------------------------------------------------------
 466                  
 467                          Uint32 i = 0;
 468                  
 469 dave.sudlik 1.42         Boolean isValid = false;
 470                  
 471 david.dillard 1.48         if (hostname[0].isDigit())
 472 kumpf         1.34         {
 473 dave.sudlik   1.42             //--------------------------------------------------------------
 474 chip          1.47             // Attempt to validate an IP address, but keep in mind that it
 475 dave.sudlik   1.42             // might be a host name, since the leading character can now be
 476                                // a digit.
 477                                //--------------------------------------------------------------
 478                                isValid = true;
 479 kumpf         1.34 
 480                                for (Uint32 octet=1; octet<=4; octet++)
 481                                {
 482                                    Uint32 octetValue = 0;
 483                    
 484 dave.sudlik   1.42                 //----------------------------------------------------------
 485                                    // If a non-digit is encountered in the input parameter,
 486                                    // then break from here and attempt to validate as host name.
 487                                    //----------------------------------------------------------
 488 david.dillard 1.48                 if (!hostname[i].isDigit())
 489 kumpf         1.34                 {
 490 dave.sudlik   1.42                     isValid = false;
 491                                        break;
 492 kumpf         1.34                 }
 493                    
 494 david.dillard 1.48                 while (hostname[i].isDigit())  // skip over digits
 495 kumpf         1.34                 {
 496                                        octetValue = octetValue*10 + (hostname[i] - '0');
 497                                        i++;
 498                                    }
 499                    
 500                                    if (octetValue > 255)
 501                                    {
 502 dave.sudlik   1.42                     isValid = false;
 503                                        break;
 504 kumpf         1.34                 }
 505                    
 506 dave.sudlik   1.42                 // Check for invalid character in IP address
 507 kumpf         1.34                 if ((octet != 4) && (hostname[i++] != '.'))
 508                                    {
 509 dave.sudlik   1.42                     isValid = false;
 510                                        break;
 511                                    }
 512                    
 513                                    // Check for the case where it's a valid host name that happens
 514                                    // to have 4 (or more) leading all-numeric host segments.
 515                                    if ((octet == 4) && (hostname[i] != ':') && hostname[i] != char(0))
 516                                    {
 517                                        isValid = false;
 518                                        break;
 519 kumpf         1.34                 }
 520                                }
 521                            }
 522 dave.sudlik   1.42         if (!isValid)   // if it is not a valid IP address
 523 kumpf         1.34         {
 524 dave.sudlik   1.42             i = 0;  // reset index for host name check
 525                    
 526                                // Validate a host name
 527                                isValid = true;
 528 kumpf         1.34 
 529                                Boolean expectHostSegment = true;
 530 dave.sudlik   1.42             Boolean hostSegmentIsNumeric;
 531 kumpf         1.34 
 532                                while (expectHostSegment == true)
 533                                {
 534                                    expectHostSegment = false;
 535 dave.sudlik   1.42                 hostSegmentIsNumeric = true; // assume all-numeric host segment
 536 kumpf         1.34 
 537 david.dillard 1.48                 if (!hostname[i].isAlnum())
 538 kumpf         1.34                 {
 539                                        return false;
 540                                    }
 541                    
 542 david.dillard 1.48                 while (hostname[i].isAlnum() || (hostname[i] == '-'))
 543 kumpf         1.34                 {
 544 dave.sudlik   1.42                     // If a non-digit is encountered, set "all-numeric"
 545                                        // flag to false
 546 david.dillard 1.48                     if (hostname[i].isAlpha() || (hostname[i] == '-')) {
 547 dave.sudlik   1.42                         hostSegmentIsNumeric = false;
 548                                        }
 549 kumpf         1.34                     i++;
 550                                    }
 551                    
 552                                    if (hostname[i] == '.')
 553                                    {
 554                                        i++;
 555                                        expectHostSegment = true;
 556                                    }
 557                                }
 558 dave.sudlik   1.42             // If the last Host Segment is all numeric, then return false.
 559                                // RFC 1123 says "highest-level component label will be alphabetic".
 560                                if (hostSegmentIsNumeric) {
 561                                    return false;
 562                                }
 563                            }
 564                    
 565                            if (!isValid) // if not a valid IP address or host name
 566                            {
 567                                return false;
 568 kumpf         1.34         }
 569                    
 570                            // Check for a port number:
 571                    
 572                            if (hostname[i] == ':')
 573                            {
 574 david.dillard 1.48             if (!hostname[++i].isDigit())
 575 kumpf         1.34             {
 576                                    return false;
 577                                }
 578 chip          1.47 
 579 david.dillard 1.48             while (hostname[++i].isDigit());
 580 kumpf         1.34         }
 581                    
 582                            return (hostname[i] == char(0));
 583                        }
 584                    
 585 kumpf         1.2      //
 586                        // Contains port as well (e.g., myhost:1234).
 587                        //
 588                        String _host;
 589                    
 590 kumpf         1.7      CIMNamespaceName _nameSpace;
 591                        CIMName _className;
 592 kumpf         1.16     Array<CIMKeyBinding> _keyBindings;
 593 kumpf         1.2  };
 594                    
 595                    
 596 a.arora       1.39 CIMObjectPath::CIMObjectPath()
 597 kumpf         1.2  {
 598 a.arora       1.39     _rep = new CIMObjectPathRep();
 599 kumpf         1.2  }
 600                    
 601 a.arora       1.39 CIMObjectPath::CIMObjectPath(const CIMObjectPath& x)
 602 kumpf         1.2  {
 603 a.arora       1.39     _rep = new CIMObjectPathRep(*x._rep);
 604 kumpf         1.2  }
 605                    
 606                    CIMObjectPath::CIMObjectPath(const String& objectName)
 607                    {
 608                        // Test the objectName out to see if we get an exception
 609                        CIMObjectPath tmpRef;
 610                        tmpRef.set(objectName);
 611                    
 612 a.arora       1.39     _rep = new CIMObjectPathRep(*tmpRef._rep);
 613 kumpf         1.2  }
 614                    
 615                    CIMObjectPath::CIMObjectPath(
 616                        const String& host,
 617 kumpf         1.7      const CIMNamespaceName& nameSpace,
 618                        const CIMName& className,
 619 kumpf         1.16     const Array<CIMKeyBinding>& keyBindings)
 620 kumpf         1.2  {
 621                        // Test the objectName out to see if we get an exception
 622                        CIMObjectPath tmpRef;
 623                        tmpRef.set(host, nameSpace, className, keyBindings);
 624                    
 625 a.arora       1.39     _rep = new CIMObjectPathRep(*tmpRef._rep);
 626 kumpf         1.2  }
 627                    
 628                    CIMObjectPath::~CIMObjectPath()
 629                    {
 630 a.arora       1.39     delete _rep;
 631 kumpf         1.2  }
 632                    
 633                    CIMObjectPath& CIMObjectPath::operator=(const CIMObjectPath& x)
 634                    {
 635 a.arora       1.39     *_rep = *x._rep;
 636 kumpf         1.2      return *this;
 637                    }
 638                    
 639                    void CIMObjectPath::clear()
 640                    {
 641                        _rep->_host.clear();
 642                        _rep->_nameSpace.clear();
 643                        _rep->_className.clear();
 644                        _rep->_keyBindings.clear();
 645                    }
 646                    
 647                    void CIMObjectPath::set(
 648                        const String& host,
 649 kumpf         1.7      const CIMNamespaceName& nameSpace,
 650                        const CIMName& className,
 651 kumpf         1.16     const Array<CIMKeyBinding>& keyBindings)
 652 kumpf         1.2  {
 653                       setHost(host);
 654                       setNameSpace(nameSpace);
 655                       setClassName(className);
 656                       setKeyBindings(keyBindings);
 657                    }
 658                    
 659 kumpf         1.22 Boolean _parseHostElement(
 660 kumpf         1.2      const String& objectName,
 661                        char*& p,
 662 kumpf         1.9      String& host)
 663 kumpf         1.2  {
 664                        // See if there is a host name (true if it begins with "//"):
 665 kumpf         1.32     // Host is of the form <hostname>:<port> and begins with "//"
 666 kumpf         1.2      // and ends with "/":
 667                    
 668                        if (p[0] != '/' || p[1] != '/')
 669                        {
 670                            return false;
 671                        }
 672                    
 673                        p += 2;
 674                    
 675 kumpf         1.34     char* slash = strchr(p, '/');
 676                        if (!slash)
 677 chuck         1.23     {
 678 kumpf         1.34         throw MalformedObjectNameException(objectName);
 679 kumpf         1.2      }
 680                    
 681 kumpf         1.34     String hostname = String(p, (Uint32)(slash - p));
 682                        if (!CIMObjectPathRep::isValidHostname(hostname))
 683 kumpf         1.2      {
 684 kumpf         1.10         throw MalformedObjectNameException(objectName);
 685 kumpf         1.2      }
 686 kumpf         1.34     host = hostname;
 687 kumpf         1.2  
 688 kumpf         1.31     // Do not step past the '/'; it will be consumed by the namespace parser
 689 kumpf         1.34     p = slash;
 690 kumpf         1.2  
 691                        return true;
 692                    }
 693                    
 694 kumpf         1.22 Boolean _parseNamespaceElement(
 695 kumpf         1.2      const String& objectName,
 696                        char*& p,
 697 kumpf         1.7      CIMNamespaceName& nameSpace)
 698 kumpf         1.2  {
 699                        // If we don't find a valid namespace name followed by a ':', we
 700                        // assume we're not looking at a namespace name.
 701                    
 702 kumpf         1.7      char* colon = strchr(p, ':');
 703                        if (!colon)
 704                        {
 705                            return false;
 706                        }
 707                    
 708 chip          1.47     // A ':' as part of a keybinding value should not be interpreted as
 709 kumpf         1.25     // a namespace delimiter.  Since keybinding pairs follow the first '.'
 710                        // in the object path string, the ':' delimiter only counts if it
 711                        // appears before the '.'.
 712                    
 713                        char* dot = strchr(p, '.');
 714                        if (dot && (dot < colon))
 715                        {
 716                            return false;
 717                        }
 718                    
 719 kumpf         1.2      //----------------------------------------------------------------------
 720                        // Validate the namespace path.  Namespaces must match the following
 721                        // regular expression: "[A-Za-z_]+(/[A-Za-z_]+)*"
 722                        //----------------------------------------------------------------------
 723                    
 724 david         1.27     String namespaceName = String(p, (Uint32)(colon - p));
 725 kumpf         1.7      if (!CIMNamespaceName::legal(namespaceName))
 726 kumpf         1.2      {
 727 kumpf         1.10         throw MalformedObjectNameException(objectName);
 728 kumpf         1.2      }
 729 kumpf         1.7      nameSpace = namespaceName;
 730 kumpf         1.2  
 731 kumpf         1.7      p = colon+1;
 732 kumpf         1.2      return true;
 733                    }
 734                    
 735                    /**
 736 kumpf         1.29     ATTN-RK: The DMTF specification for the string form of an
 737                        object path makes it impossible for a parser to distinguish
 738                        between a key values of String type and Reference type.
 739                    
 740                        Given the ambiguity, this implementation takes a guess at the
 741                        type of a quoted key value.  If the value can be parsed into
 742                        a CIMObjectPath with at least one key binding, the type is
 743                        set to REFERENCE.  Otherwise, the type is set to STRING.
 744                        Note: This algorithm appears to be in line with what the Sun
 745                        WBEM Services implementation does.
 746                    
 747                        To be totally correct, it would be necessary to retrieve the
 748                        class definition and look up the types of the key properties
 749                        to determine how to interpret the key values.  This is clearly
 750                        too inefficient for internal transformations between
 751                        CIMObjectPaths and String values.
 752 kumpf         1.2  */
 753 kumpf         1.22 void _parseKeyBindingPairs(
 754 kumpf         1.2      const String& objectName,
 755                        char*& p,
 756 chip          1.47     Array<CIMKeyBinding>& keyBindings)
 757 kumpf         1.2  {
 758                        // Get the key-value pairs:
 759                    
 760                        while (*p)
 761                        {
 762                            // Get key part:
 763                    
 764 kumpf         1.5          char* equalsign = strchr(p, '=');
 765                            if (!equalsign)
 766 kumpf         1.2          {
 767 kumpf         1.10             throw MalformedObjectNameException(objectName);
 768 kumpf         1.2          }
 769                    
 770 kumpf         1.5          *equalsign = 0;
 771 kumpf         1.2  
 772 kumpf         1.17         if (!CIMName::legal(p))
 773 kumpf         1.10             throw MalformedObjectNameException(objectName);
 774 kumpf         1.2  
 775 kumpf         1.17         CIMName keyName (p);
 776                    
 777 kumpf         1.2          // Get the value part:
 778                    
 779                            String valueString;
 780 kumpf         1.5          p = equalsign + 1;
 781 kumpf         1.16         CIMKeyBinding::Type type;
 782 kumpf         1.2  
 783 kumpf         1.29         if (*p == '"')
 784 kumpf         1.2          {
 785 kumpf         1.29             // Could be CIMKeyBinding::STRING or CIMKeyBinding::REFERENCE
 786                    
 787 kumpf         1.2              p++;
 788                    
 789                                while (*p && *p != '"')
 790                                {
 791                                    if (*p == '\\')
 792 kumpf         1.28                 {
 793 kumpf         1.2                      *p++;
 794                    
 795 kumpf         1.28                     if ((*p != '\\') && (*p != '"'))
 796                                        {
 797                                            throw MalformedObjectNameException(objectName);
 798                                        }
 799                                    }
 800                    
 801 kumpf         1.2                  valueString.append(*p++);
 802                                }
 803                    
 804                                if (*p++ != '"')
 805 kumpf         1.10                 throw MalformedObjectNameException(objectName);
 806 kumpf         1.2  
 807 kumpf         1.29             /*
 808                                    Guess at the type of this quoted key value.  If the value
 809                                    can be parsed into a CIMObjectPath with at least one key
 810                                    binding, the type is assumed to be a REFERENCE.  Otherwise,
 811                                    the type is set to STRING.  (See method header for details.)
 812                                 */
 813 kumpf         1.16             type = CIMKeyBinding::STRING;
 814 kumpf         1.2  
 815 kumpf         1.29             try
 816 kumpf         1.2              {
 817 kumpf         1.29                 CIMObjectPath testForPath(valueString);
 818                                    if (testForPath.getKeyBindings().size() > 0)
 819 kumpf         1.28                 {
 820 kumpf         1.29                     // We've found a reference value!
 821                                        type = CIMKeyBinding::REFERENCE;
 822 kumpf         1.28                 }
 823 kumpf         1.2              }
 824 david.dillard 1.41             catch (const Exception &)
 825 kumpf         1.29             {
 826                                    // Not a reference value; leave type as STRING
 827                                }
 828 kumpf         1.2          }
 829                            else if (toupper(*p) == 'T' || toupper(*p) == 'F')
 830                            {
 831 kumpf         1.16             type = CIMKeyBinding::BOOLEAN;
 832 kumpf         1.2  
 833                                char* r = p;
 834                                Uint32 n = 0;
 835                    
 836                                while (*r && *r != ',')
 837                                {
 838                                    *r = toupper(*r);
 839                                    r++;
 840                                    n++;
 841                                }
 842                    
 843                                if (!(((strncmp(p, "TRUE", n) == 0) && n == 4) ||
 844                                      ((strncmp(p, "FALSE", n) == 0) && n == 5)))
 845 kumpf         1.10                 throw MalformedObjectNameException(objectName);
 846 kumpf         1.2  
 847                                valueString.assign(p, n);
 848                    
 849                                p = p + n;
 850                            }
 851                            else
 852                            {
 853 kumpf         1.16             type = CIMKeyBinding::NUMERIC;
 854 kumpf         1.2  
 855                                char* r = p;
 856                                Uint32 n = 0;
 857                    
 858                                while (*r && *r != ',')
 859                                {
 860                                    r++;
 861                                    n++;
 862                                }
 863                    
 864                                Boolean isComma = false;
 865                                if (*r)
 866                                {
 867                                    *r = '\0';
 868                                    isComma = true;
 869                                }
 870                    
 871                                Sint64 x;
 872                    
 873                                if (!XmlReader::stringToSignedInteger(p, x))
 874 kumpf         1.10                 throw MalformedObjectNameException(objectName);
 875 kumpf         1.2  
 876                                valueString.assign(p, n);
 877                    
 878                                if (isComma)
 879                                {
 880                                    *r = ',';
 881                                }
 882                    
 883                                p = p + n;
 884                            }
 885                    
 886 chip          1.47         keyBindings.append(CIMKeyBinding(keyName.getString (), valueString,
 887 kumpf         1.17             type));
 888 kumpf         1.2  
 889                            if (*p)
 890                            {
 891                                if (*p++ != ',')
 892                                {
 893 kumpf         1.10                 throw MalformedObjectNameException(objectName);
 894 kumpf         1.2              }
 895                            }
 896                        }
 897                    
 898                        _BubbleSort(keyBindings);
 899                    }
 900                    
 901 chip          1.47 void CIMObjectPath::set(const String& objectName)
 902 kumpf         1.2  {
 903                        clear();
 904                    
 905                        //--------------------------------------------------------------------------
 906                        // We will extract components from an object name. Here is an sample
 907                        // object name:
 908                        //
 909                        //     //atp:9999/root/cimv25:TennisPlayer.first="Patrick",last="Rafter"
 910                        //--------------------------------------------------------------------------
 911                    
 912                        // Convert to a C String first:
 913                    
 914 david         1.37     CString pCString = objectName.getCString();
 915 kumpf         1.26     char* p = const_cast<char*>((const char*) pCString);
 916 kumpf         1.2      Boolean gotHost;
 917                        Boolean gotNamespace;
 918                    
 919                        gotHost = _parseHostElement(objectName, p, _rep->_host);
 920                        gotNamespace = _parseNamespaceElement(objectName, p, _rep->_nameSpace);
 921                    
 922                        if (gotHost && !gotNamespace)
 923                        {
 924 kumpf         1.10         throw MalformedObjectNameException(objectName);
 925 kumpf         1.2      }
 926                    
 927                        // Extract the class name:
 928                    
 929                        char* dot = strchr(p, '.');
 930                    
 931                        if (!dot)
 932                        {
 933                            if (!CIMName::legal(p))
 934                            {
 935 kumpf         1.10             throw MalformedObjectNameException(objectName);
 936 kumpf         1.2          }
 937                    
 938                            // ATTN: remove this later: a reference should only be able to hold
 939                            // an instance name.
 940                    
 941 kumpf         1.17         _rep->_className = CIMName (p);
 942 kumpf         1.2          return;
 943                        }
 944                    
 945 david         1.27     String className = String(p, (Uint32)(dot - p));
 946 kumpf         1.7      if (!CIMName::legal(className))
 947                        {
 948 kumpf         1.10         throw MalformedObjectNameException(objectName);
 949 kumpf         1.7      }
 950                        _rep->_className = className;
 951 kumpf         1.2  
 952                        // Advance past dot:
 953                    
 954                        p = dot + 1;
 955                    
 956                        _parseKeyBindingPairs(objectName, p, _rep->_keyBindings);
 957                    }
 958                    
 959                    CIMObjectPath& CIMObjectPath::operator=(const String& objectName)
 960                    {
 961                        set(objectName);
 962                        return *this;
 963                    }
 964                    
 965                    const String& CIMObjectPath::getHost() const
 966                    {
 967                        return _rep->_host;
 968                    }
 969                    
 970                    void CIMObjectPath::setHost(const String& host)
 971                    {
 972 kumpf         1.35     if ((host != String::EMPTY) && !CIMObjectPathRep::isValidHostname(host))
 973                        {
 974                            throw MalformedObjectNameException(host);
 975                        }
 976                    
 977 kumpf         1.2      _rep->_host = host;
 978                    }
 979                    
 980 kumpf         1.7  const CIMNamespaceName& CIMObjectPath::getNameSpace() const
 981 kumpf         1.2  {
 982                        return _rep->_nameSpace;
 983                    }
 984                    
 985 kumpf         1.7  void CIMObjectPath::setNameSpace(const CIMNamespaceName& nameSpace)
 986 kumpf         1.2  {
 987                       _rep->_nameSpace = nameSpace;
 988                    }
 989                    
 990 kumpf         1.7  const CIMName& CIMObjectPath::getClassName() const
 991 kumpf         1.2  {
 992                        return _rep->_className;
 993                    }
 994                    
 995 kumpf         1.7  void CIMObjectPath::setClassName(const CIMName& className)
 996 kumpf         1.2  {
 997                        _rep->_className = className;
 998                    }
 999                    
1000 kumpf         1.16 const Array<CIMKeyBinding>& CIMObjectPath::getKeyBindings() const
1001 kumpf         1.2  {
1002                        return _rep->_keyBindings;
1003                    }
1004                    
1005 kumpf         1.16 void CIMObjectPath::setKeyBindings(const Array<CIMKeyBinding>& keyBindings)
1006 kumpf         1.2  {
1007                        _rep->_keyBindings = keyBindings;
1008                        _BubbleSort(_rep->_keyBindings);
1009                    }
1010                    
1011 kumpf         1.15 String CIMObjectPath::toString() const
1012 kumpf         1.2  {
1013                        String objectName;
1014                    
1015                        // Get the host:
1016                    
1017 kumpf         1.15     if (_rep->_host.size())
1018 kumpf         1.2      {
1019                            objectName = "//";
1020 kumpf         1.13         objectName.append(_rep->_host);
1021                            objectName.append("/");
1022 kumpf         1.2      }
1023                    
1024                        // Get the namespace (if we have a host name, we must write namespace):
1025                    
1026 kumpf         1.7      if (!_rep->_nameSpace.isNull() || _rep->_host.size())
1027 kumpf         1.2      {
1028 kumpf         1.17         objectName.append(_rep->_nameSpace.getString ());
1029 kumpf         1.13         objectName.append(":");
1030 kumpf         1.2      }
1031                    
1032                        // Get the class name:
1033                    
1034 kumpf         1.17     objectName.append(getClassName().getString ());
1035 kumpf         1.2  
1036 kumpf         1.9      //
1037                        //  ATTN-CAKG-P2-20020726:  The following condition does not correctly
1038                        //  distinguish instanceNames from classNames in every case
1039                        //  The instanceName of a singleton instance of a keyless class has no
1040                        //  key bindings
1041                        //
1042                        if (_rep->_keyBindings.size () != 0)
1043 kumpf         1.2      {
1044                            objectName.append('.');
1045                    
1046                            // Append each key-value pair:
1047                    
1048 kumpf         1.16         const Array<CIMKeyBinding>& keyBindings = getKeyBindings();
1049 kumpf         1.2  
1050                            for (Uint32 i = 0, n = keyBindings.size(); i < n; i++)
1051                            {
1052 kumpf         1.17             objectName.append(keyBindings[i].getName().getString ());
1053 kumpf         1.2              objectName.append('=');
1054                    
1055                                const String& value = _escapeSpecialCharacters(
1056                                    keyBindings[i].getValue());
1057                    
1058 kumpf         1.16             CIMKeyBinding::Type type = keyBindings[i].getType();
1059 chip          1.47 
1060 kumpf         1.16             if (type == CIMKeyBinding::STRING || type == CIMKeyBinding::REFERENCE)
1061 kumpf         1.2                  objectName.append('"');
1062                    
1063                                objectName.append(value);
1064                    
1065 kumpf         1.16             if (type == CIMKeyBinding::STRING || type == CIMKeyBinding::REFERENCE)
1066 kumpf         1.2                  objectName.append('"');
1067                    
1068                                if (i + 1 != n)
1069                                    objectName.append(',');
1070                            }
1071                        }
1072                    
1073                        return objectName;
1074                    }
1075                    
1076 kumpf         1.15 String CIMObjectPath::_toStringCanonical() const
1077 kumpf         1.2  {
1078                        CIMObjectPath ref = *this;
1079                    
1080 kumpf         1.33     // Normalize hostname by changing to lower case
1081 chip          1.47     ref._rep->_host.toLower(); // ICU_TODO:
1082 kumpf         1.2  
1083 kumpf         1.33     // Normalize namespace by changing to lower case
1084                        if (!ref._rep->_nameSpace.isNull())
1085                        {
1086                            String nameSpaceLower = ref._rep->_nameSpace.getString();
1087 chip          1.47         nameSpaceLower.toLower(); // ICU_TODO:
1088 kumpf         1.33         ref._rep->_nameSpace = nameSpaceLower;
1089                        }
1090                    
1091                        // Normalize class name by changing to lower case
1092                        if (!ref._rep->_className.isNull())
1093                        {
1094                            String classNameLower = ref._rep->_className.getString();
1095 chip          1.47         classNameLower.toLower(); // ICU_TODO:
1096 kumpf         1.33         ref._rep->_className = classNameLower;
1097                        }
1098 kumpf         1.2  
1099                        for (Uint32 i = 0, n = ref._rep->_keyBindings.size(); i < n; i++)
1100                        {
1101 kumpf         1.33         // Normalize key binding name by changing to lower case
1102                            if (!ref._rep->_keyBindings[i]._rep->_name.isNull())
1103                            {
1104 chip          1.47             String keyBindingNameLower =
1105 kumpf         1.33                 ref._rep->_keyBindings[i]._rep->_name.getString();
1106                                keyBindingNameLower.toLower(); // ICU_TODO:
1107                                ref._rep->_keyBindings[i]._rep->_name = keyBindingNameLower;
1108                            }
1109                    
1110                            // Normalize the key value
1111                            switch (ref._rep->_keyBindings[i]._rep->_type)
1112                            {
1113                            case CIMKeyBinding::REFERENCE:
1114                                try
1115                                {
1116                                    // Convert reference to CIMObjectPath and recurse
1117                                    ref._rep->_keyBindings[i]._rep->_value =
1118                                        CIMObjectPath(ref._rep->_keyBindings[i]._rep->_value).
1119                                            _toStringCanonical();
1120                                }
1121                                catch (Exception&)
1122                                {
1123                                    // Leave value unchanged if the CIMObjectPath parsing fails
1124                                }
1125                                break;
1126 kumpf         1.33         case CIMKeyBinding::BOOLEAN:
1127                                // Normalize the boolean string by changing to lower case
1128                                ref._rep->_keyBindings[i]._rep->_value.toLower(); // ICU_TODO:
1129                                break;
1130                            case CIMKeyBinding::NUMERIC:
1131                                // Normalize the numeric string by converting to integer and back
1132                                Uint64 uValue;
1133                                Sint64 sValue;
1134                                // First try converting to unsigned integer
1135                                if (XmlReader::stringToUnsignedInteger(
1136                                        ref._rep->_keyBindings[i]._rep->_value.getCString(),
1137                                            uValue))
1138                                {
1139                                    char buffer[32];  // Should need 21 chars max
1140                                    sprintf(buffer, "%" PEGASUS_64BIT_CONVERSION_WIDTH "u", uValue);
1141                                    ref._rep->_keyBindings[i]._rep->_value = String(buffer);
1142                                }
1143                                // Next try converting to signed integer
1144                                else if (XmlReader::stringToSignedInteger(
1145                                             ref._rep->_keyBindings[i]._rep->_value.getCString(),
1146                                                 sValue))
1147 kumpf         1.33             {
1148                                    char buffer[32];  // Should need 21 chars max
1149                                    sprintf(buffer, "%" PEGASUS_64BIT_CONVERSION_WIDTH "d", sValue);
1150                                    ref._rep->_keyBindings[i]._rep->_value = String(buffer);
1151                                }
1152                                // Leave value unchanged if it cannot be converted to an integer
1153                                break;
1154                            default:  // CIMKeyBinding::STRING
1155                                // No normalization required for STRING
1156                                break;
1157                            }
1158 kumpf         1.2      }
1159                    
1160 kumpf         1.33     // Note: key bindings are sorted when set in the CIMObjectPath
1161 kumpf         1.12 
1162 kumpf         1.15     return ref.toString();
1163 kumpf         1.2  }
1164                    
1165                    Boolean CIMObjectPath::identical(const CIMObjectPath& x) const
1166                    {
1167                        return
1168 kumpf         1.33         String::equalNoCase(_rep->_host, x._rep->_host) &&
1169 kumpf         1.7          _rep->_nameSpace.equal(x._rep->_nameSpace) &&
1170                            _rep->_className.equal(x._rep->_className) &&
1171 kumpf         1.2          _rep->_keyBindings == x._rep->_keyBindings;
1172                    }
1173                    
1174                    Uint32 CIMObjectPath::makeHashCode() const
1175                    {
1176 kumpf         1.12     return HashFunc<String>::hash(_toStringCanonical());
1177 kumpf         1.2  }
1178                    
1179                    Boolean operator==(const CIMObjectPath& x, const CIMObjectPath& y)
1180                    {
1181                        return x.identical(y);
1182                    }
1183                    
1184                    Boolean operator!=(const CIMObjectPath& x, const CIMObjectPath& y)
1185                    {
1186                        return !operator==(x, y);
1187                    }
1188                    
1189 chip          1.1  PEGASUS_NAMESPACE_END

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2