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