00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00021 #include "query.h"
00022
00023 #include <kdebug.h>
00024 #include <klocale.h>
00025
00026 #include <QtCore/QDateTime>
00027 #include <QtCore/QVariant>
00028 #include <QtXml/QDomDocument>
00029
00030 using namespace KXmlRpc;
00031
00038 namespace KXmlRpc {
00039
00048 class Result
00049 {
00050 friend class Query;
00051 friend class Query::Private;
00052
00053 public:
00057 Result();
00058
00062 ~Result();
00063
00070 bool success() const;
00071
00077 int errorCode() const;
00078
00084 QString errorString() const;
00085
00089 QList<QVariant> data() const;
00090
00091 private:
00092 bool mSuccess;
00093 int mErrorCode;
00094 QString mErrorString;
00095 QList<QVariant> mData;
00096 };
00097
00098 }
00099
00100 KXmlRpc::Result::Result()
00101 {
00102 }
00103
00104 KXmlRpc::Result::~Result()
00105 {
00106 }
00107
00108 bool KXmlRpc::Result::success() const
00109 {
00110 return mSuccess;
00111 }
00112
00113 int KXmlRpc::Result::errorCode() const
00114 {
00115 return mErrorCode;
00116 }
00117
00118 QString KXmlRpc::Result::errorString() const
00119 {
00120 return mErrorString;
00121 }
00122
00123 QList<QVariant> KXmlRpc::Result::data() const
00124 {
00125 return mData;
00126 }
00127
00128 class Query::Private
00129 {
00130 public:
00131 Private( Query *parent )
00132 : mParent( parent )
00133 {
00134 }
00135
00136 bool isMessageResponse( const QDomDocument &doc ) const;
00137 bool isFaultResponse( const QDomDocument &doc ) const;
00138
00139 Result parseMessageResponse( const QDomDocument &doc ) const;
00140 Result parseFaultResponse( const QDomDocument &doc ) const;
00141
00142 QString markupCall( const QString &method, const QList<QVariant> &args ) const;
00143 QString marshal( const QVariant &value ) const;
00144 QVariant demarshal( const QDomElement &element ) const;
00145
00146 void slotData( KIO::Job *job, const QByteArray &data );
00147 void slotResult( KJob *job );
00148
00149 Query *mParent;
00150 QByteArray mBuffer;
00151 QVariant mId;
00152 QList<KJob*> mPendingJobs;
00153 };
00154
00155 bool Query::Private::isMessageResponse( const QDomDocument &doc ) const
00156 {
00157 return doc.documentElement().firstChild().toElement().tagName().toLower()
00158 == "params";
00159 }
00160
00161 bool Query::Private::isFaultResponse( const QDomDocument &doc ) const
00162 {
00163 return doc.documentElement().firstChild().toElement().tagName().toLower()
00164 == "fault";
00165 }
00166
00167 Result Query::Private::parseMessageResponse( const QDomDocument &doc ) const
00168 {
00169 Result response;
00170 response.mSuccess = true;
00171
00172 QDomNode paramNode = doc.documentElement().firstChild().firstChild();
00173 while ( !paramNode.isNull() ) {
00174 response.mData << demarshal( paramNode.firstChild().toElement() );
00175 paramNode = paramNode.nextSibling();
00176 }
00177
00178 return response;
00179 }
00180
00181 Result Query::Private::parseFaultResponse( const QDomDocument &doc ) const
00182 {
00183 Result response;
00184 response.mSuccess = false;
00185
00186 QDomNode errorNode = doc.documentElement().firstChild().firstChild();
00187 const QVariant errorVariant = demarshal( errorNode.toElement() );
00188 response.mErrorCode = errorVariant.toMap() [ "faultCode" ].toInt();
00189 response.mErrorString = errorVariant.toMap() [ "faultString" ].toString();
00190
00191 return response;
00192 }
00193
00194 QString Query::Private::markupCall( const QString &cmd,
00195 const QList<QVariant> &args ) const
00196 {
00197 QString markup = "<?xml version=\"1.0\" ?>\r\n<methodCall>\r\n";
00198
00199 markup += "<methodName>" + cmd + "</methodName>\r\n";
00200
00201 if ( !args.isEmpty() ) {
00202
00203 markup += "<params>\r\n";
00204 QList<QVariant>::ConstIterator it = args.begin();
00205 QList<QVariant>::ConstIterator end = args.end();
00206 for ( ; it != end; ++it ) {
00207 markup += "<param>\r\n" + marshal( *it ) + "</param>\r\n";
00208 }
00209 markup += "</params>\r\n";
00210 }
00211
00212 markup += "</methodCall>\r\n";
00213
00214 return markup;
00215 }
00216
00217 QString Query::Private::marshal( const QVariant &arg ) const
00218 {
00219 switch ( arg.type() ) {
00220
00221 case QVariant::String:
00222 return "<value><string><![CDATA[" + arg.toString() + "]]></string></value>\r\n";
00223 case QVariant::StringList:
00224 {
00225 QStringList data = arg.toStringList();
00226 QStringListIterator dataIterator(data);
00227 QString markup;
00228 markup += "<value><array><data>";
00229 while ( dataIterator.hasNext() ) {
00230 markup += "<string><![CDATA[" + dataIterator.next() + "]]></string>\r\n";
00231 }
00232 markup += "</data></array></value>";
00233 return markup;
00234 }
00235 case QVariant::Int:
00236 return "<value><int>" + QString::number( arg.toInt() ) + "</int></value>\r\n";
00237 case QVariant::Double:
00238 return "<value><double>" + QString::number( arg.toDouble() ) + "</double></value>\r\n";
00239 case QVariant::Bool:
00240 {
00241 QString markup = "<value><boolean>";
00242 markup += arg.toBool() ? "1" : "0";
00243 markup += "</boolean></value>\r\n";
00244 return markup;
00245 }
00246 case QVariant::ByteArray:
00247 return "<value><base64>" + arg.toByteArray().toBase64() + "</base64></value>\r\n";
00248 case QVariant::DateTime:
00249 {
00250 return "<value><dateTime.iso8601>" + arg.toDateTime().toString( Qt::ISODate ) + "</dateTime.iso8601></value>\r\n";
00251 }
00252 case QVariant::List:
00253 {
00254 QString markup = "<value><array><data>\r\n";
00255 const QList<QVariant> args = arg.toList();
00256 QList<QVariant>::ConstIterator it = args.begin();
00257 QList<QVariant>::ConstIterator end = args.end();
00258 for ( ; it != end; ++it ) {
00259 markup += marshal( *it );
00260 }
00261 markup += "</data></array></value>\r\n";
00262 return markup;
00263 }
00264 case QVariant::Map:
00265 {
00266 QString markup = "<value><struct>\r\n";
00267 QMap<QString, QVariant> map = arg.toMap();
00268 QMap<QString, QVariant>::ConstIterator it = map.begin();
00269 QMap<QString, QVariant>::ConstIterator end = map.end();
00270 for ( ; it != end; ++it ) {
00271 markup += "<member>\r\n";
00272 markup += "<name>" + it.key() + "</name>\r\n";
00273 markup += marshal( it.value() );
00274 markup += "</member>\r\n";
00275 }
00276 markup += "</struct></value>\r\n";
00277 return markup;
00278 }
00279 default:
00280 kWarning() << "Failed to marshal unknown variant type:" << arg.type();
00281 };
00282
00283 return QString();
00284 }
00285
00286 QVariant Query::Private::demarshal( const QDomElement &element ) const
00287 {
00288 Q_ASSERT( element.tagName().toLower() == "value" );
00289
00290 const QDomElement typeElement = element.firstChild().toElement();
00291 const QString typeName = typeElement.tagName().toLower();
00292
00293 if ( typeName == "string" ) {
00294 return QVariant( typeElement.text() );
00295 } else if ( typeName == "i4" || typeName == "int" ) {
00296 return QVariant( typeElement.text().toInt() );
00297 } else if ( typeName == "double" ) {
00298 return QVariant( typeElement.text().toDouble() );
00299 } else if ( typeName == "boolean" ) {
00300
00301 if ( typeElement.text().toLower() == "true" || typeElement.text() == "1" ) {
00302 return QVariant( true );
00303 } else {
00304 return QVariant( false );
00305 }
00306 } else if ( typeName == "base64" ) {
00307 return QVariant( QByteArray::fromBase64( typeElement.text().toLatin1() ) );
00308 } else if ( typeName == "datetime" || typeName == "datetime.iso8601" ) {
00309 QDateTime date;
00310 QString dateText = typeElement.text();
00311
00312 if ( 17 <= dateText.length() && dateText.length() <= 18 &&
00313 dateText.at( 4 ) != '-' && dateText.at( 11 ) == ':' ) {
00314 if ( dateText.endsWith( 'Z' ) ) {
00315 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ssZ" );
00316 } else {
00317 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ss" );
00318 }
00319 } else {
00320 date = QDateTime::fromString( dateText, Qt::ISODate );
00321 }
00322 return QVariant( date );
00323 } else if ( typeName == "array" ) {
00324 QList<QVariant> values;
00325 QDomNode valueNode = typeElement.firstChild().firstChild();
00326 while ( !valueNode.isNull() ) {
00327 values << demarshal( valueNode.toElement() );
00328 valueNode = valueNode.nextSibling();
00329 }
00330 return QVariant( values );
00331 } else if ( typeName == "struct" ) {
00332
00333 QMap<QString, QVariant> map;
00334 QDomNode memberNode = typeElement.firstChild();
00335 while ( !memberNode.isNull() ) {
00336 const QString key = memberNode.toElement().elementsByTagName(
00337 "name" ).item( 0 ).toElement().text();
00338 const QVariant data = demarshal( memberNode.toElement().elementsByTagName(
00339 "value" ).item( 0 ).toElement() );
00340 map[ key ] = data;
00341 memberNode = memberNode.nextSibling();
00342 }
00343 return QVariant( map );
00344 } else {
00345 kWarning() << "Cannot demarshal unknown type" << typeName;
00346 }
00347 return QVariant();
00348 }
00349
00350 void Query::Private::slotData( KIO::Job *, const QByteArray &data )
00351 {
00352 unsigned int oldSize = mBuffer.size();
00353 mBuffer.resize( oldSize + data.size() );
00354 memcpy( mBuffer.data() + oldSize, data.data(), data.size() );
00355 }
00356
00357 void Query::Private::slotResult( KJob *job )
00358 {
00359 mPendingJobs.removeAll( job );
00360
00361 if ( job->error() != 0 ) {
00362 emit mParent->fault( job->error(), job->errorString(), mId );
00363 emit mParent->finished( mParent );
00364 return;
00365 }
00366
00367 const QString data = QString::fromUtf8( mBuffer.data(), mBuffer.size() );
00368
00369 QDomDocument doc;
00370 QString errMsg;
00371 int errLine, errCol;
00372 if ( !doc.setContent( data, false, &errMsg, &errLine, &errCol ) ) {
00373 emit mParent->fault( -1, i18n( "Received invalid XML markup: %1 at %2:%3",
00374 errMsg, errLine, errCol ), mId );
00375 emit mParent->finished( mParent );
00376 return;
00377 }
00378
00379 mBuffer.truncate( 0 );
00380
00381 if ( isMessageResponse( doc ) ) {
00382 emit mParent->message( parseMessageResponse( doc ).data(), mId );
00383 } else if ( isFaultResponse( doc ) ) {
00384 emit mParent->fault( parseFaultResponse( doc ).errorCode(),
00385 parseFaultResponse( doc ).errorString(), mId );
00386 } else {
00387 emit mParent->fault( 1, i18n( "Unknown type of XML markup received" ),
00388 mId );
00389 }
00390
00391 emit mParent->finished( mParent );
00392 }
00393
00394 Query *Query::create( const QVariant &id, QObject *parent )
00395 {
00396 return new Query( id, parent );
00397 }
00398
00399 void Query::call( const QString &server,
00400 const QString &method,
00401 const QList<QVariant> &args,
00402 const QMap<QString, QString> &jobMetaData)
00403 {
00404
00405 const QString xmlMarkup = d->markupCall( method, args );
00406
00407 QMap<QString, QString>::const_iterator mapIter;
00408 QByteArray postData;
00409 QDataStream stream( &postData, QIODevice::WriteOnly );
00410 stream.writeRawData( xmlMarkup.toUtf8(), xmlMarkup.toUtf8().length() );
00411
00412 KIO::TransferJob *job = KIO::http_post( KUrl( server ), postData, KIO::HideProgressInfo );
00413
00414 if ( !job ) {
00415 kWarning() << "Unable to create KIO job for" << server;
00416 return;
00417 }
00418
00419 job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" );
00420 job->addMetaData( "ConnectTimeout", "50" );
00421
00422 for ( mapIter = jobMetaData.begin(); mapIter != jobMetaData.end(); mapIter++ ) {
00423 job->addMetaData( mapIter.key(), mapIter.value() );
00424 }
00425
00426 connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00427 this, SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
00428 connect( job, SIGNAL( result( KJob * ) ),
00429 this, SLOT( slotResult( KJob * ) ) );
00430
00431 d->mPendingJobs.append( job );
00432 }
00433
00434 Query::Query( const QVariant &id, QObject *parent )
00435 : QObject( parent ), d( new Private( this ) )
00436 {
00437 d->mId = id;
00438 }
00439
00440 Query::~Query()
00441 {
00442 QList<KJob*>::Iterator it;
00443 for ( it = d->mPendingJobs.begin(); it != d->mPendingJobs.end(); ++it ) {
00444 (*it)->kill();
00445 }
00446 delete d;
00447 }
00448
00449 #include "query.moc"
00450