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 += "<value><string><![CDATA[" + dataIterator.next() + "]]></string></value>\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>" +
00251 arg.toDateTime().toString( Qt::ISODate ) +
00252 "</dateTime.iso8601></value>\r\n";
00253 }
00254 case QVariant::List:
00255 {
00256 QString markup = "<value><array><data>\r\n";
00257 const QList<QVariant> args = arg.toList();
00258 QList<QVariant>::ConstIterator it = args.begin();
00259 QList<QVariant>::ConstIterator end = args.end();
00260 for ( ; it != end; ++it ) {
00261 markup += marshal( *it );
00262 }
00263 markup += "</data></array></value>\r\n";
00264 return markup;
00265 }
00266 case QVariant::Map:
00267 {
00268 QString markup = "<value><struct>\r\n";
00269 QMap<QString, QVariant> map = arg.toMap();
00270 QMap<QString, QVariant>::ConstIterator it = map.constBegin();
00271 QMap<QString, QVariant>::ConstIterator end = map.constEnd();
00272 for ( ; it != end; ++it ) {
00273 markup += "<member>\r\n";
00274 markup += "<name>" + it.key() + "</name>\r\n";
00275 markup += marshal( it.value() );
00276 markup += "</member>\r\n";
00277 }
00278 markup += "</struct></value>\r\n";
00279 return markup;
00280 }
00281 default:
00282 kWarning() << "Failed to marshal unknown variant type:" << arg.type();
00283 };
00284
00285 return QString();
00286 }
00287
00288 QVariant Query::Private::demarshal( const QDomElement &element ) const
00289 {
00290 Q_ASSERT( element.tagName().toLower() == "value" );
00291
00292 const QDomElement typeElement = element.firstChild().toElement();
00293 const QString typeName = typeElement.tagName().toLower();
00294
00295 if ( typeName == "string" ) {
00296 return QVariant( typeElement.text() );
00297 } else if ( typeName == "i4" || typeName == "int" ) {
00298 return QVariant( typeElement.text().toInt() );
00299 } else if ( typeName == "double" ) {
00300 return QVariant( typeElement.text().toDouble() );
00301 } else if ( typeName == "boolean" ) {
00302
00303 if ( typeElement.text().toLower() == "true" || typeElement.text() == "1" ) {
00304 return QVariant( true );
00305 } else {
00306 return QVariant( false );
00307 }
00308 } else if ( typeName == "base64" ) {
00309 return QVariant( QByteArray::fromBase64( typeElement.text().toLatin1() ) );
00310 } else if ( typeName == "datetime" || typeName == "datetime.iso8601" ) {
00311 QDateTime date;
00312 QString dateText = typeElement.text();
00313
00314 if ( 17 <= dateText.length() && dateText.length() <= 18 &&
00315 dateText.at( 4 ) != '-' && dateText.at( 11 ) == ':' ) {
00316 if ( dateText.endsWith( 'Z' ) ) {
00317 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ssZ" );
00318 } else {
00319 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ss" );
00320 }
00321 } else {
00322 date = QDateTime::fromString( dateText, Qt::ISODate );
00323 }
00324 return QVariant( date );
00325 } else if ( typeName == "array" ) {
00326 QList<QVariant> values;
00327 QDomNode valueNode = typeElement.firstChild().firstChild();
00328 while ( !valueNode.isNull() ) {
00329 values << demarshal( valueNode.toElement() );
00330 valueNode = valueNode.nextSibling();
00331 }
00332 return QVariant( values );
00333 } else if ( typeName == "struct" ) {
00334
00335 QMap<QString, QVariant> map;
00336 QDomNode memberNode = typeElement.firstChild();
00337 while ( !memberNode.isNull() ) {
00338 const QString key = memberNode.toElement().elementsByTagName(
00339 "name" ).item( 0 ).toElement().text();
00340 const QVariant data = demarshal( memberNode.toElement().elementsByTagName(
00341 "value" ).item( 0 ).toElement() );
00342 map[ key ] = data;
00343 memberNode = memberNode.nextSibling();
00344 }
00345 return QVariant( map );
00346 } else {
00347 kWarning() << "Cannot demarshal unknown type" << typeName;
00348 }
00349 return QVariant();
00350 }
00351
00352 void Query::Private::slotData( KIO::Job *, const QByteArray &data )
00353 {
00354 unsigned int oldSize = mBuffer.size();
00355 mBuffer.resize( oldSize + data.size() );
00356 memcpy( mBuffer.data() + oldSize, data.data(), data.size() );
00357 }
00358
00359 void Query::Private::slotResult( KJob *job )
00360 {
00361 mPendingJobs.removeAll( job );
00362
00363 if ( job->error() != 0 ) {
00364 emit mParent->fault( job->error(), job->errorString(), mId );
00365 emit mParent->finished( mParent );
00366 return;
00367 }
00368
00369 const QString data = QString::fromUtf8( mBuffer.data(), mBuffer.size() );
00370
00371 QDomDocument doc;
00372 QString errMsg;
00373 int errLine, errCol;
00374 if ( !doc.setContent( data, false, &errMsg, &errLine, &errCol ) ) {
00375 emit mParent->fault( -1, i18n( "Received invalid XML markup: %1 at %2:%3",
00376 errMsg, errLine, errCol ), mId );
00377 emit mParent->finished( mParent );
00378 return;
00379 }
00380
00381 mBuffer.truncate( 0 );
00382
00383 if ( isMessageResponse( doc ) ) {
00384 emit mParent->message( parseMessageResponse( doc ).data(), mId );
00385 } else if ( isFaultResponse( doc ) ) {
00386 emit mParent->fault( parseFaultResponse( doc ).errorCode(),
00387 parseFaultResponse( doc ).errorString(), mId );
00388 } else {
00389 emit mParent->fault( 1, i18n( "Unknown type of XML markup received" ),
00390 mId );
00391 }
00392
00393 emit mParent->finished( mParent );
00394 }
00395
00396 Query *Query::create( const QVariant &id, QObject *parent )
00397 {
00398 return new Query( id, parent );
00399 }
00400
00401 void Query::call( const QString &server,
00402 const QString &method,
00403 const QList<QVariant> &args,
00404 const QMap<QString, QString> &jobMetaData )
00405 {
00406
00407 const QString xmlMarkup = d->markupCall( method, args );
00408
00409 QMap<QString, QString>::const_iterator mapIter;
00410 QByteArray postData;
00411 QDataStream stream( &postData, QIODevice::WriteOnly );
00412 stream.writeRawData( xmlMarkup.toUtf8(), xmlMarkup.toUtf8().length() );
00413
00414 KIO::TransferJob *job = KIO::http_post( KUrl( server ), postData, KIO::HideProgressInfo );
00415
00416 if ( !job ) {
00417 kWarning() << "Unable to create KIO job for" << server;
00418 return;
00419 }
00420
00421 job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" );
00422 job->addMetaData( "ConnectTimeout", "50" );
00423
00424 for ( mapIter = jobMetaData.begin(); mapIter != jobMetaData.end(); ++mapIter ) {
00425 job->addMetaData( mapIter.key(), mapIter.value() );
00426 }
00427
00428 connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00429 this, SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
00430 connect( job, SIGNAL( result( KJob * ) ),
00431 this, SLOT( slotResult( KJob * ) ) );
00432
00433 d->mPendingJobs.append( job );
00434 }
00435
00436 Query::Query( const QVariant &id, QObject *parent )
00437 : QObject( parent ), d( new Private( this ) )
00438 {
00439 d->mId = id;
00440 }
00441
00442 Query::~Query()
00443 {
00444 QList<KJob*>::Iterator it;
00445 for ( it = d->mPendingJobs.begin(); it != d->mPendingJobs.end(); ++it ) {
00446 (*it)->kill();
00447 }
00448 delete d;
00449 }
00450
00451 #include "query.moc"
00452