1 //
2 // Copyright (c) ZeroC, Inc. All rights reserved.
3 //
4
5 #include <Operation.h>
6 #include <Proxy.h>
7 #include <Types.h>
8 #include <Util.h>
9 #include <Ice/Communicator.h>
10 #include <Ice/Initialize.h>
11 #include <Ice/LocalException.h>
12 #include <Ice/Logger.h>
13 #include <Ice/Properties.h>
14 #include <Ice/Proxy.h>
15 #include <Slice/RubyUtil.h>
16
17 using namespace std;
18 using namespace IceRuby;
19 using namespace Slice::Ruby;
20
21 static VALUE _operationClass;
22
23 namespace IceRuby
24 {
25
26 class ParamInfo : public UnmarshalCallback
27 {
28 public:
29
30 virtual void unmarshaled(VALUE, VALUE, void*);
31
32 TypeInfoPtr type;
33 bool optional;
34 int tag;
35 int pos;
36 };
37 typedef IceUtil::Handle<ParamInfo> ParamInfoPtr;
38 typedef list<ParamInfoPtr> ParamInfoList;
39
40 class OperationI : public Operation
41 {
42 public:
43
44 OperationI(VALUE, VALUE, VALUE, VALUE, VALUE, VALUE, VALUE, VALUE, VALUE);
45
46 virtual VALUE invoke(const Ice::ObjectPrx&, VALUE, VALUE);
47 virtual void deprecate(const string&);
48
49 private:
50
51 string _name;
52 Ice::OperationMode _mode;
53 Ice::OperationMode _sendMode;
54 bool _amd;
55 Ice::FormatType _format;
56 ParamInfoList _inParams;
57 ParamInfoList _optionalInParams;
58 ParamInfoList _outParams;
59 ParamInfoList _optionalOutParams;
60 ParamInfoPtr _returnType;
61 ExceptionInfoList _exceptions;
62 string _dispatchName;
63 bool _sendsClasses;
64 bool _returnsClasses;
65 string _deprecateMessage;
66
67 void convertParams(VALUE, ParamInfoList&, int, bool&);
68 ParamInfoPtr convertParam(VALUE, int);
69 void prepareRequest(const Ice::ObjectPrx&, VALUE, Ice::OutputStream*, pair<const Ice::Byte*, const Ice::Byte*>&);
70 VALUE unmarshalResults(const vector<Ice::Byte>&, const Ice::CommunicatorPtr&);
71 VALUE unmarshalException(const vector<Ice::Byte>&, const Ice::CommunicatorPtr&);
72 bool validateException(VALUE) const;
73 void checkTwowayOnly(const Ice::ObjectPrx&) const;
74 };
75 typedef IceUtil::Handle<OperationI> OperationIPtr;
76
77 class UserExceptionFactory : public Ice::UserExceptionFactory
78 {
79 public:
80
createAndThrow(const string & id)81 virtual void createAndThrow(const string& id)
82 {
83 ExceptionInfoPtr info = lookupExceptionInfo(id);
84 if(info)
85 {
86 throw ExceptionReader(info);
87 }
88 }
89 };
90
91 }
92
93 extern "C"
94 void
IceRuby_Operation_free(OperationPtr * p)95 IceRuby_Operation_free(OperationPtr* p)
96 {
97 delete p;
98 }
99
100 extern "C"
101 VALUE
IceRuby_defineOperation(VALUE,VALUE name,VALUE mode,VALUE sendMode,VALUE amd,VALUE format,VALUE inParams,VALUE outParams,VALUE returnType,VALUE exceptions)102 IceRuby_defineOperation(VALUE /*self*/, VALUE name, VALUE mode, VALUE sendMode, VALUE amd, VALUE format, VALUE inParams,
103 VALUE outParams, VALUE returnType, VALUE exceptions)
104 {
105 ICE_RUBY_TRY
106 {
107 OperationIPtr op = new OperationI(name, mode, sendMode, amd, format, inParams, outParams, returnType,
108 exceptions);
109 return Data_Wrap_Struct(_operationClass, 0, IceRuby_Operation_free, new OperationPtr(op));
110 }
111 ICE_RUBY_CATCH
112 return Qnil;
113 }
114
115 extern "C"
116 VALUE
IceRuby_Operation_invoke(VALUE self,VALUE proxy,VALUE opArgs,VALUE ctx)117 IceRuby_Operation_invoke(VALUE self, VALUE proxy, VALUE opArgs, VALUE ctx)
118 {
119 ICE_RUBY_TRY
120 {
121 assert(TYPE(opArgs) == T_ARRAY);
122
123 OperationPtr op = getOperation(self);
124 assert(op);
125 return op->invoke(getProxy(proxy), opArgs, ctx);
126 }
127 ICE_RUBY_CATCH
128 return Qnil;
129 }
130
131 extern "C"
132 VALUE
IceRuby_Operation_deprecate(VALUE self,VALUE msg)133 IceRuby_Operation_deprecate(VALUE self, VALUE msg)
134 {
135 ICE_RUBY_TRY
136 {
137 OperationPtr op = getOperation(self);
138 assert(op);
139 op->deprecate(getString(msg));
140 }
141 ICE_RUBY_CATCH
142 return Qnil;
143 }
144
145 //
146 // Operation implementation.
147 //
~Operation()148 IceRuby::Operation::~Operation()
149 {
150 }
151
152 //
153 // ParamInfo implementation.
154 //
155 void
unmarshaled(VALUE val,VALUE target,void * closure)156 IceRuby::ParamInfo::unmarshaled(VALUE val, VALUE target, void* closure)
157 {
158 assert(TYPE(target) == T_ARRAY);
159 #ifdef ICE_64
160 long i = static_cast<long>(reinterpret_cast<long long>(closure));
161 #else
162 long i = reinterpret_cast<long>(closure);
163 #endif
164 RARRAY_ASET(target, i, val);
165 }
166
167 //
168 // OperationI implementation.
169 //
OperationI(VALUE name,VALUE mode,VALUE sendMode,VALUE amd,VALUE format,VALUE inParams,VALUE outParams,VALUE returnType,VALUE exceptions)170 IceRuby::OperationI::OperationI(VALUE name, VALUE mode, VALUE sendMode, VALUE amd, VALUE format, VALUE inParams,
171 VALUE outParams, VALUE returnType, VALUE exceptions)
172 {
173 _name = getString(name);
174 _amd = amd == Qtrue;
175 if(_amd)
176 {
177 _dispatchName = fixIdent(_name, IdentNormal) + "_async";
178 }
179 else
180 {
181 _dispatchName = fixIdent(_name, IdentNormal);
182 }
183
184 //
185 // mode
186 //
187 volatile VALUE modeValue = callRuby(rb_funcall, mode, rb_intern("to_i"), 0);
188 assert(TYPE(modeValue) == T_FIXNUM);
189 _mode = static_cast<Ice::OperationMode>(FIX2LONG(modeValue));
190
191 //
192 // sendMode
193 //
194 volatile VALUE sendModeValue = callRuby(rb_funcall, sendMode, rb_intern("to_i"), 0);
195 assert(TYPE(sendModeValue) == T_FIXNUM);
196 _sendMode = static_cast<Ice::OperationMode>(FIX2LONG(sendModeValue));
197
198 //
199 // format
200 //
201 if(format == Qnil)
202 {
203 _format = Ice::DefaultFormat;
204 }
205 else
206 {
207 volatile VALUE formatValue = callRuby(rb_funcall, format, rb_intern("to_i"), 0);
208 assert(TYPE(formatValue) == T_FIXNUM);
209 _format = static_cast<Ice::FormatType>(FIX2LONG(formatValue));
210 }
211
212 //
213 // returnType
214 //
215 _returnsClasses = false;
216 if(!NIL_P(returnType))
217 {
218 _returnType = convertParam(returnType, 0);
219 if(!_returnType->optional)
220 {
221 _returnsClasses = _returnType->type->usesClasses();
222 }
223 }
224
225 //
226 // inParams
227 //
228 _sendsClasses = false;
229 convertParams(inParams, _inParams, 0, _sendsClasses);
230
231 //
232 // outParams
233 //
234 convertParams(outParams, _outParams, NIL_P(returnType) ? 0 : 1, _returnsClasses);
235
236 class SortFn
237 {
238 public:
239 static bool compare(const ParamInfoPtr& lhs, const ParamInfoPtr& rhs)
240 {
241 return lhs->tag < rhs->tag;
242 }
243
244 static bool isRequired(const ParamInfoPtr& i)
245 {
246 return !i->optional;
247 }
248 };
249
250 //
251 // The inParams list represents the parameters in the order of declaration.
252 // We also need a sorted list of optional parameters.
253 //
254 ParamInfoList l = _inParams;
255 copy(l.begin(), remove_if(l.begin(), l.end(), SortFn::isRequired), back_inserter(_optionalInParams));
256 _optionalInParams.sort(SortFn::compare);
257
258 //
259 // The outParams list represents the parameters in the order of declaration.
260 // We also need a sorted list of optional parameters. If the return value is
261 // optional, we must include it in this list.
262 //
263 l = _outParams;
264 copy(l.begin(), remove_if(l.begin(), l.end(), SortFn::isRequired), back_inserter(_optionalOutParams));
265 if(_returnType && _returnType->optional)
266 {
267 _optionalOutParams.push_back(_returnType);
268 }
269 _optionalOutParams.sort(SortFn::compare);
270
271 //
272 // exceptions
273 //
274 for(long i = 0; i < RARRAY_LEN(exceptions); ++i)
275 {
276 _exceptions.push_back(getException(RARRAY_AREF(exceptions, i)));
277 }
278 }
279
280 VALUE
invoke(const Ice::ObjectPrx & proxy,VALUE args,VALUE hctx)281 IceRuby::OperationI::invoke(const Ice::ObjectPrx& proxy, VALUE args, VALUE hctx)
282 {
283 Ice::CommunicatorPtr communicator = proxy->ice_getCommunicator();
284
285 //
286 // Marshal the input parameters to a byte sequence.
287 //
288 Ice::OutputStream os(communicator);
289 pair<const Ice::Byte*, const Ice::Byte*> params;
290 prepareRequest(proxy, args, &os, params);
291
292 if(!_deprecateMessage.empty())
293 {
294 rb_warning("%s", _deprecateMessage.c_str());
295 _deprecateMessage.clear(); // Only show the warning once.
296 }
297
298 checkTwowayOnly(proxy);
299
300 //
301 // Invoke the operation.
302 //
303 Ice::ByteSeq result;
304 bool status;
305
306 if(!NIL_P(hctx))
307 {
308 Ice::Context ctx;
309 if(!hashToContext(hctx, ctx))
310 {
311 throw RubyException(rb_eArgError, "context argument must be nil or a hash");
312 }
313
314 status = proxy->ice_invoke(_name, _sendMode, params, result, ctx);
315 }
316 else
317 {
318 status = proxy->ice_invoke(_name, _sendMode, params, result);
319 }
320
321 //
322 // Process the reply.
323 //
324 if(proxy->ice_isTwoway())
325 {
326 if(!status)
327 {
328 //
329 // Unmarshal a user exception.
330 //
331 volatile VALUE ex = unmarshalException(result, communicator);
332 throw RubyException(ex);
333 }
334 else if(_outParams.size() > 0 || _returnType)
335 {
336 //
337 // Unmarshal the results. If there is more than one value to be returned, then return them
338 // in an array of the form [result, outParam1, ...]. Otherwise just return the value.
339 //
340 volatile VALUE results = unmarshalResults(result, communicator);
341
342 if(RARRAY_LEN(results)> 1)
343 {
344 return results;
345 }
346 else
347 {
348 return RARRAY_AREF(results, 0);
349 }
350 }
351 }
352
353 return Qnil;
354 }
355
356 void
deprecate(const string & msg)357 IceRuby::OperationI::deprecate(const string& msg)
358 {
359 if(!msg.empty())
360 {
361 _deprecateMessage = msg;
362 }
363 else
364 {
365 _deprecateMessage = "operation " + _name + " is deprecated";
366 }
367 }
368
369 void
convertParams(VALUE v,ParamInfoList & params,int posOffset,bool & usesClasses)370 IceRuby::OperationI::convertParams(VALUE v, ParamInfoList& params, int posOffset, bool& usesClasses)
371 {
372 assert(TYPE(v) == T_ARRAY);
373
374 for(long i = 0; i < RARRAY_LEN(v); ++i)
375 {
376 ParamInfoPtr param = convertParam(RARRAY_AREF(v, i), i + posOffset);
377 params.push_back(param);
378 if(!param->optional && !usesClasses)
379 {
380 usesClasses = param->type->usesClasses();
381 }
382 }
383 }
384
385 ParamInfoPtr
convertParam(VALUE v,int pos)386 IceRuby::OperationI::convertParam(VALUE v, int pos)
387 {
388 assert(TYPE(v) == T_ARRAY);
389 ParamInfoPtr param = new ParamInfo;
390 param->type = getType(RARRAY_AREF(v, 0));
391 param->optional = static_cast<bool>(RTEST(RARRAY_AREF(v, 1)));
392 param->tag = static_cast<int>(getInteger(RARRAY_AREF(v, 2)));
393 param->pos = pos;
394 return param;
395 }
396
397 void
prepareRequest(const Ice::ObjectPrx & proxy,VALUE args,Ice::OutputStream * os,pair<const Ice::Byte *,const Ice::Byte * > & params)398 IceRuby::OperationI::prepareRequest(const Ice::ObjectPrx& proxy, VALUE args, Ice::OutputStream* os,
399 pair<const Ice::Byte*, const Ice::Byte*>& params)
400 {
401 params.first = params.second = static_cast<const Ice::Byte*>(0);
402
403 //
404 // Validate the number of arguments.
405 //
406 long argc = RARRAY_LEN(args);
407 long paramCount = static_cast<long>(_inParams.size());
408 if(argc != paramCount)
409 {
410 string fixedName = fixIdent(_name, IdentNormal);
411 throw RubyException(rb_eArgError, "%s expects %ld in parameters", fixedName.c_str(), paramCount);
412 }
413
414 if(!_inParams.empty())
415 {
416 //
417 // Marshal the in parameters.
418 //
419 os->startEncapsulation(proxy->ice_getEncodingVersion(), _format);
420
421 ObjectMap objectMap;
422 ParamInfoList::iterator p;
423
424 //
425 // Validate the supplied arguments.
426 //
427 for(p = _inParams.begin(); p != _inParams.end(); ++p)
428 {
429 ParamInfoPtr info = *p;
430 volatile VALUE arg = RARRAY_AREF(args, info->pos);
431 if((!info->optional || arg != Unset) && !info->type->validate(arg))
432 {
433 string opName = fixIdent(_name, IdentNormal);
434 throw RubyException(rb_eTypeError, "invalid value for argument %ld in operation `%s'", info->pos + 1,
435 opName.c_str());
436 }
437 }
438
439 //
440 // Marshal the required parameters.
441 //
442 for(p = _inParams.begin(); p != _inParams.end(); ++p)
443 {
444 ParamInfoPtr info = *p;
445 if(!info->optional)
446 {
447 volatile VALUE arg = RARRAY_AREF(args, info->pos);
448 info->type->marshal(arg, os, &objectMap, false);
449 }
450 }
451
452 //
453 // Marshal the optional parameters.
454 //
455 for(p = _optionalInParams.begin(); p != _optionalInParams.end(); ++p)
456 {
457 ParamInfoPtr info = *p;
458 volatile VALUE arg = RARRAY_AREF(args, info->pos);
459 if(arg != Unset && os->writeOptional(info->tag, info->type->optionalFormat()))
460 {
461 info->type->marshal(arg, os, &objectMap, true);
462 }
463 }
464
465 if(_sendsClasses)
466 {
467 os->writePendingValues();
468 }
469
470 os->endEncapsulation();
471 params = os->finished();
472 }
473 }
474
475 VALUE
unmarshalResults(const vector<Ice::Byte> & bytes,const Ice::CommunicatorPtr & communicator)476 IceRuby::OperationI::unmarshalResults(const vector<Ice::Byte>& bytes, const Ice::CommunicatorPtr& communicator)
477 {
478 int numResults = static_cast<int>(_outParams.size());
479 if(_returnType)
480 {
481 numResults++;
482 }
483 assert(numResults > 0);
484
485 volatile VALUE results = createArray(numResults);
486
487 //
488 // Unmarshal the results. If there is more than one value to be returned, then return them
489 // in a tuple of the form (result, outParam1, ...). Otherwise just return the value.
490 //
491 Ice::InputStream is(communicator, bytes);
492
493 //
494 // Store a pointer to a local StreamUtil object as the stream's closure.
495 // This is necessary to support object unmarshaling (see ObjectReader).
496 //
497 StreamUtil util;
498 assert(!is.getClosure());
499 is.setClosure(&util);
500
501 is.startEncapsulation();
502
503 ParamInfoList::iterator p;
504
505 //
506 // Unmarshal the required out parameters.
507 //
508 for(p = _outParams.begin(); p != _outParams.end(); ++p)
509 {
510 ParamInfoPtr info = *p;
511 if(!info->optional)
512 {
513 void* closure = reinterpret_cast<void*>(info->pos);
514 info->type->unmarshal(&is, info, results, closure, false);
515 }
516 }
517
518 //
519 // Unmarshal the required return value, if any.
520 //
521 if(_returnType && !_returnType->optional)
522 {
523 assert(_returnType->pos == 0);
524 void* closure = reinterpret_cast<void*>(_returnType->pos);
525 _returnType->type->unmarshal(&is, _returnType, results, closure, false);
526 }
527
528 //
529 // Unmarshal the optional results. This includes an optional return value.
530 //
531 for(p = _optionalOutParams.begin(); p != _optionalOutParams.end(); ++p)
532 {
533 ParamInfoPtr info = *p;
534 if(is.readOptional(info->tag, info->type->optionalFormat()))
535 {
536 void* closure = reinterpret_cast<void*>(info->pos);
537 info->type->unmarshal(&is, info, results, closure, true);
538 }
539 else
540 {
541 RARRAY_ASET(results, info->pos, Unset);
542 }
543 }
544
545 if(_returnsClasses)
546 {
547 is.readPendingValues();
548 }
549
550 is.endEncapsulation();
551
552 util.updateSlicedData();
553
554 return results;
555 }
556
557 VALUE
unmarshalException(const vector<Ice::Byte> & bytes,const Ice::CommunicatorPtr & communicator)558 IceRuby::OperationI::unmarshalException(const vector<Ice::Byte>& bytes, const Ice::CommunicatorPtr& communicator)
559 {
560 Ice::InputStream is(communicator, bytes);
561
562 //
563 // Store a pointer to a local StreamUtil object as the stream's closure.
564 // This is necessary to support object unmarshaling (see ObjectReader).
565 //
566 StreamUtil util;
567 assert(!is.getClosure());
568 is.setClosure(&util);
569
570 is.startEncapsulation();
571
572 try
573 {
574 Ice::UserExceptionFactoryPtr factory = new UserExceptionFactory;
575 is.throwException(factory);
576 }
577 catch(const ExceptionReader& r)
578 {
579 is.endEncapsulation();
580
581 volatile VALUE ex = r.getException();
582
583 if(validateException(ex))
584 {
585 util.updateSlicedData();
586
587 Ice::SlicedDataPtr slicedData = r.getSlicedData();
588 if(slicedData)
589 {
590 StreamUtil::setSlicedDataMember(ex, slicedData);
591 }
592
593 return ex;
594 }
595 else
596 {
597 volatile VALUE cls = CLASS_OF(ex);
598 volatile VALUE path = callRuby(rb_class_path, cls);
599 assert(TYPE(path) == T_STRING);
600 Ice::UnknownUserException e(__FILE__, __LINE__);
601 e.unknown = RSTRING_PTR(path);
602 throw e;
603 }
604 }
605
606 throw Ice::UnknownUserException(__FILE__, __LINE__, "unknown exception");
607 #ifdef __SUNPRO_CC
608 return 0;
609 #endif
610 }
611
612 bool
validateException(VALUE ex) const613 IceRuby::OperationI::validateException(VALUE ex) const
614 {
615 for(ExceptionInfoList::const_iterator p = _exceptions.begin(); p != _exceptions.end(); ++p)
616 {
617 if(callRuby(rb_obj_is_kind_of, ex, (*p)->rubyClass))
618 {
619 return true;
620 }
621 }
622
623 return false;
624 }
625
626 void
checkTwowayOnly(const Ice::ObjectPrx & proxy) const627 IceRuby::OperationI::checkTwowayOnly(const Ice::ObjectPrx& proxy) const
628 {
629 if((_returnType != 0 || !_outParams.empty()) && !proxy->ice_isTwoway())
630 {
631 Ice::TwowayOnlyException ex(__FILE__, __LINE__);
632 ex.operation = _name;
633 throw ex;
634 }
635 }
636
637 bool
initOperation(VALUE iceModule)638 IceRuby::initOperation(VALUE iceModule)
639 {
640 rb_define_module_function(iceModule, "__defineOperation", CAST_METHOD(IceRuby_defineOperation), 9);
641
642 //
643 // Define a class to represent an operation.
644 //
645 _operationClass = rb_define_class_under(iceModule, "IceRuby_Operation", rb_cObject);
646
647 rb_define_method(_operationClass, "invoke", CAST_METHOD(IceRuby_Operation_invoke), 3);
648 rb_define_method(_operationClass, "deprecate", CAST_METHOD(IceRuby_Operation_deprecate), 1);
649
650 return true;
651 }
652
653 IceRuby::OperationPtr
getOperation(VALUE obj)654 IceRuby::getOperation(VALUE obj)
655 {
656 assert(TYPE(obj) == T_DATA);
657 assert(rb_obj_is_instance_of(obj, _operationClass) == Qtrue);
658 OperationPtr* p = reinterpret_cast<OperationPtr*>(DATA_PTR(obj));
659 return *p;
660 }
661