platform/reactors/TransformReactor.cpp

00001 // ------------------------------------------------------------------------
00002 // Pion is a development platform for building Reactors that process Events
00003 // ------------------------------------------------------------------------
00004 // Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
00005 //
00006 // Pion is free software: you can redistribute it and/or modify it under the
00007 // terms of the GNU Affero General Public License as published by the Free
00008 // Software Foundation, either version 3 of the License, or (at your option)
00009 // any later version.
00010 //
00011 // Pion is distributed in the hope that it will be useful, but WITHOUT ANY
00012 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00013 // FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
00014 // more details.
00015 //
00016 // You should have received a copy of the GNU Affero General Public License
00017 // along with Pion.  If not, see <http://www.gnu.org/licenses/>.
00018 //
00019 
00020 #include <pion/platform/ConfigManager.hpp>
00021 #include <pion/platform/ReactionEngine.hpp>
00022 #include "TransformReactor.hpp"
00023 
00024 using namespace pion::platform;
00025 
00026 
00027 namespace pion {        // begin namespace pion
00028 namespace plugins {     // begin namespace plugins
00029 
00030 
00031 // static members of TransformReactor
00032 
00033 const std::string           TransformReactor::OUTGOING_EVENT_ELEMENT_NAME = "OutgoingEvent";
00034 const std::string           TransformReactor::DELIVER_ORIGINAL_NAME = "DeliverOriginal";
00035 const std::string           TransformReactor::COPY_ORIGINAL_ELEMENT_NAME = "CopyOriginal";
00036 const std::string           TransformReactor::TRANSFORMATION_ELEMENT_NAME = "Transformation";
00037 const std::string           TransformReactor::TERM_ELEMENT_NAME = "Term";
00038 const std::string           TransformReactor::TYPE_ELEMENT_NAME = "Type";
00039 
00040 
00041 /*
00042  *  This is the spec, using annotated XML
00043 
00044 <TransformReactor>
00045     <OutgoingEvent>obj-term</OutgoingEvent>
00046     <CopyOriginal>all-terms|if-not-defined|none</CopyOriginal>          -> DEFAULT: if-not-defined
00047     <DeliverOriginal>always|if-not-changed|never</DeliveryOriginal>     -> DEFAULT: never
00048 [rpt]   <Transformation>
00049             <Term>dst-term</Term>
00050             <Type>AssignValue|AssignTerm|Lookup|Rules|Regex</Type>
00051             [see TransformReactor/Transformations/Type]
00052 [/rpt]  </Transformation>
00053 </TransformReactor>
00054 
00055 TransformReactor/Transformations/Type = AssignValue
00056             <Type>AssignValue</Type>
00057             <Value>escape(value)</Value>
00058 
00059 TransformReactor/Transformations/Type = AssignTerm
00060             <Type>AssignTerm</Type>
00061             <Value>src-term</Value>
00062 
00063 TransformReactor/Transformations/Type = Lookup
00064             <Type>Lookup</Type>
00065             <LookupTerm>src-term</LookupTerm>
00066 [opt]       <Match>escape(regexp)</Match>
00067 [opt]       <Format>escape(format)</Format>
00068 [opt]       <DefaultAction>leave-undefined|src-term|output|fixedvalue</DefaultAction>
00069 [opt]       <DefaultValue>escape(text)</DefaultValue>
00070 [rpt/]      <Lookup key="escape(key)">escape(value)</Lookup>
00071 
00072 TransformReactor/Transformations/Type = Rules
00073             <Type>Rules</Type>
00074             <StopOnFirstMatch>true|false</StopOnFirstMatch>         -> DEFAULT: true
00075 [rpt]       <Rule>
00076                 <Term>src-term</Term>
00077                 <Type>test-type</Type>
00078                 <Value>escape(test-value)</Value>
00079                 <SetValue>escape(set-value)</SetValue>
00080 [/rpt]      </Rule>
00081 
00082 TransformReactor/Transformations/Type = Regex
00083             <Type>Regex</Type>
00084             <SourceTerm>src-term</SourceTerm>
00085 [rpt]       <Regex exp="escape(key)">escape(format)</Regex>
00086 
00087 JoinTerm: Iterates over source-term's values, joins values using separator-string (defaults to none), produces single-value term.  Optionally eliminates duplicates from source-term (defaults to false).  If only one value results, then no separator.
00088   <Transformation>
00089     <Term>urn:vocab:foo#single-value</Term>
00090     <Type>JoinTerm</Type>
00091     <Value sep="," uniq="true">urn:vocab:foo#multi-value</Value>
00092   </Transformation>
00093  
00094 SplitTerm: Iterates over source-term's values, splits each value on separator-string (required), produces multi-value term.  If separator does not exist in value, value is preserved unchanged.
00095   <Transformation>
00096     <Term>urn:vocab:foo#multi-value</Term>
00097     <Type>SplitTerm</Type>
00098     <Value sep=",">urn:vocab:foo#single-or-multi-value</Value>
00099   </Transformation>
00100 
00101  */
00102 
00103 // TransformReactor member functions
00104 
00105 void TransformReactor::setConfig(const Vocabulary& v, const xmlNodePtr config_ptr)
00106 {
00107     // first set config options for the Reactor base class
00108     ConfigWriteLock cfg_lock(*this);
00109     Reactor::setConfig(v, config_ptr);
00110 
00111     // clear the current configuration
00112     m_transforms.clear();
00113 
00114     // Outgoing Event type -- i.e. what will the outgoing event be transformed into
00115     // Default (UNDEFINED_TERM_REF) -- make it the same as incoming event type
00116     //  <OutgoingEvent>obj-term</OutgoingEvent>
00117     m_event_type = Vocabulary::UNDEFINED_TERM_REF;
00118     std::string event_type_str;
00119     if (ConfigManager::getConfigOption(OUTGOING_EVENT_ELEMENT_NAME, event_type_str, config_ptr))
00120     {
00121         if (!event_type_str.empty())
00122             m_event_type = v.findTerm(event_type_str);
00123     }
00124 
00125     // This really doesn't make much sense anymore -- you can wire the delivery of the original right through
00126     // it would make sense, if it was possible to deliver "if-not-changed" but TR2 always changes...
00127     //  <DeliverOriginal>always|if-not-changed|never</DeliveryOriginal>     -> DEFAULT: never
00128     m_deliver_original = DO_NEVER;
00129     std::string deliver_original_str;
00130     if (ConfigManager::getConfigOption(DELIVER_ORIGINAL_NAME, deliver_original_str, config_ptr))
00131     {
00132         if (deliver_original_str == "true" || deliver_original_str == "always")
00133             m_deliver_original = DO_ALWAYS;
00134         else if (deliver_original_str == "if-not-changed")
00135             m_deliver_original = DO_SOMETIMES;
00136         // Could add code to throw if d_o_s is not "never"
00137     }
00138 
00139     // What fields/terms of the original event should be COPIED into the new event
00140     // <CopyOriginal>all-terms|if-not-defined|none</CopyOriginal>           -> DEFAULT: if-not-defined
00141     m_copy_original = COPY_UNCHANGED;
00142     std::string copy_original_str;
00143     if (ConfigManager::getConfigOption(COPY_ORIGINAL_ELEMENT_NAME, copy_original_str, config_ptr))
00144     {
00145         if (copy_original_str == "all-terms")
00146             m_copy_original = COPY_ALL;
00147         else if (copy_original_str == "none")
00148             m_copy_original = COPY_NONE;
00149         // Could add code to throw if c_o_s is not "if-not-defined"
00150     }
00151 
00152     // now, parse transformation rules
00153     // [rpt]    <Transformation>
00154     xmlNodePtr transformation_node = config_ptr;
00155     while ( (transformation_node = ConfigManager::findConfigNodeByName(TRANSFORMATION_ELEMENT_NAME, transformation_node)) != NULL)
00156     {
00157         // parse new Transformation rule
00158 
00159         // get the Term used for the Transformation rule
00160         //  <Term>src-term</Term>
00161         std::string term_id;
00162         if (! ConfigManager::getConfigOption(TERM_ELEMENT_NAME, term_id,
00163                                              transformation_node->children))
00164             throw EmptyTermException(getId());
00165 
00166         // make sure that the Term is valid
00167         const Vocabulary::TermRef term_ref = v.findTerm(term_id);
00168         if (term_ref == Vocabulary::UNDEFINED_TERM_REF)
00169             throw UnknownTermException(getId());
00170 
00171         // get the Type of transformation
00172         //  <Type>AssignValue|AssignTerm|Lookup|Rules</Type>
00173         std::string type_str;
00174         if (! ConfigManager::getConfigOption(TYPE_ELEMENT_NAME, type_str,
00175                                              transformation_node->children))
00176             throw EmptyTypeException(getId());  // TODO: Improve the error message
00177 
00178         // Add the transformation
00179         Transform *new_transform;
00180         if (type_str == "AssignValue")
00181             new_transform = new TransformAssignValue(v, v[term_ref], transformation_node->children);
00182         else if (type_str == "AssignTerm")
00183             new_transform = new TransformAssignTerm(v, v[term_ref], transformation_node->children);
00184         else if (type_str == "Lookup")
00185             new_transform = new TransformLookup(v, v[term_ref], transformation_node->children);
00186         else if (type_str == "Rules")
00187             new_transform = new TransformRules(v, v[term_ref], transformation_node->children);
00188         else if (type_str == "Regex")
00189             new_transform = new TransformRegex(v, v[term_ref], transformation_node->children);
00190         else if (type_str == "SplitTerm")
00191             new_transform = new TransformSplitTerm(v, v[term_ref], transformation_node->children);
00192         else if (type_str == "JoinTerm")
00193             new_transform = new TransformJoinTerm(v, v[term_ref], transformation_node->children);
00194         else if (type_str == "URLEncode")
00195             new_transform = new TransformURLEncode(v, v[term_ref], transformation_node->children);
00196         else if (type_str == "URLDecode")
00197             new_transform = new TransformURLDecode(v, v[term_ref], transformation_node->children);
00198         else
00199             throw InvalidTransformation(type_str);
00200 
00201         m_transforms.push_back(new_transform);
00202 
00203         // step to the next Comparison rule
00204         transformation_node = transformation_node->next;
00205     }
00206 }
00207 
00208 void TransformReactor::updateVocabulary(const Vocabulary& v)
00209 {
00210     // first update anything in the Reactor base class that might be needed
00211     ConfigWriteLock cfg_lock(*this);
00212     Reactor::updateVocabulary(v);
00213 
00214     // update Vocabulary for each of the rules
00215     for (TransformChain::iterator i = m_transforms.begin(); i != m_transforms.end(); ++i) {
00216         (*i)->updateVocabulary(v);
00217     }
00218 }
00219 
00220 void TransformReactor::process(const EventPtr& e)
00221 {
00222     EventPtr new_e;
00223     // Create new event; either same type (if UNDEFINED) or defined type
00224     m_event_factory.create(new_e, m_event_type == Vocabulary::UNDEFINED_TERM_REF ? e->getType() : m_event_type);
00225 
00226     // Copy terms over from original
00227     switch (m_copy_original) {
00228         case COPY_ALL:              // Copy all terms over from original event
00229             *new_e += *e;
00230             break;
00231         case COPY_UNCHANGED:        // Copy ONLY terms, that are not defined in transformations...
00232             *new_e += *e;           // First copy all terms...
00233             // Then remove all the ones with transformations...
00234             for (TransformChain::iterator i = m_transforms.begin(); i != m_transforms.end(); i++)
00235                 (*i)->removeTerm(new_e);
00236             // TODO: Which is more efficient? Only copying the ones that are not transformed, or this?
00237             break;
00238         case COPY_NONE:             // Do not copy terms from original event
00239             break;
00240     }
00241 
00242     try {
00243         for (TransformChain::iterator i = m_transforms.begin(); i != m_transforms.end(); i++)
00244             (*i)->transform(new_e, e);      // transform   d <- s
00245     } catch (...) {
00246         // Likely Boost.regex throw
00247         if (getReactionEngine().getDebugMode())     // Are we in debug mode?
00248             stop();                                 // Yes: stop the reactor
00249         throw TransformFailureException(getId());   // Continue throw to log error
00250     }
00251 
00252     deliverEvent(new_e);            // Deliver the modified event
00253 
00254     // Transformation is done, deliver original event?
00255     if (m_deliver_original != DO_NEVER)
00256         deliverEvent(e);
00257 }
00258 
00259 
00260 }   // end namespace plugins
00261 }   // end namespace pion
00262 
00263 
00265 extern "C" PION_PLUGIN_API pion::platform::Reactor *pion_create_TransformReactor(void) {
00266     return new pion::plugins::TransformReactor();
00267 }
00268 
00270 extern "C" PION_PLUGIN_API void pion_destroy_TransformReactor(pion::plugins::TransformReactor *reactor_ptr) {
00271     delete reactor_ptr;
00272 }

Generated on Wed Apr 13 16:38:34 2011 for pion-platform by  doxygen 1.4.7