Save This Page
Home » velocity-1.5 » org.apache » velocity » servlet » [javadoc | source]
    1   package org.apache.velocity.servlet;
    2   
    3   /*
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
   10    * with the License.  You may obtain a copy of the License at
   11    *
   12    *   http://www.apache.org/licenses/LICENSE-2.0
   13    *
   14    * Unless required by applicable law or agreed to in writing,
   15    * software distributed under the License is distributed on an
   16    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   17    * KIND, either express or implied.  See the License for the
   18    * specific language governing permissions and limitations
   19    * under the License.    
   20    */
   21   
   22   import java.io.FileNotFoundException;
   23   import java.io.IOException;
   24   import java.io.OutputStreamWriter;
   25   import java.io.PrintWriter;
   26   import java.io.StringWriter;
   27   import java.io.UnsupportedEncodingException;
   28   import java.util.Properties;
   29   
   30   import javax.servlet.ServletConfig;
   31   import javax.servlet.ServletContext;
   32   import javax.servlet.ServletException;
   33   import javax.servlet.ServletOutputStream;
   34   import javax.servlet.http.HttpServlet;
   35   import javax.servlet.http.HttpServletRequest;
   36   import javax.servlet.http.HttpServletResponse;
   37   
   38   import org.apache.velocity.Template;
   39   import org.apache.velocity.VelocityContext;
   40   import org.apache.velocity.app.Velocity;
   41   import org.apache.velocity.context.Context;
   42   import org.apache.velocity.exception.MethodInvocationException;
   43   import org.apache.velocity.exception.ParseErrorException;
   44   import org.apache.velocity.exception.ResourceNotFoundException;
   45   import org.apache.velocity.io.VelocityWriter;
   46   import org.apache.velocity.runtime.RuntimeConstants;
   47   import org.apache.velocity.runtime.RuntimeSingleton;
   48   import org.apache.velocity.util.SimplePool;
   49   
   50   /**
   51    * Base class which simplifies the use of Velocity with Servlets.
   52    * Extend this class, implement the <code>handleRequest()</code> method,
   53    * and add your data to the context.  Then call
   54    * <code>getTemplate("myTemplate.wm")</code>.
   55    *
   56    * This class puts some things into the context object that you should
   57    * be aware of:
   58    * <pre>
   59    * "req" - The HttpServletRequest object
   60    * "res" - The HttpServletResponse object
   61    * </pre>
   62    *
   63    * There are other methods you can override to access, alter or control
   64    * any part of the request processing chain.  Please see the javadocs for
   65    * more information on :
   66    * <ul>
   67    * <li> loadConfiguration() : for setting up the Velocity runtime
   68    * <li> createContext() : for creating and loading the Context
   69    * <li> setContentType() : for changing the content type on a request
   70    *                         by request basis
   71    * <li> handleRequest() : you <b>must</b> implement this
   72    * <li> mergeTemplate()  : the template rendering process
   73    * <li> requestCleanup() : post rendering resource or other cleanup
   74    * <li> error() : error handling
   75    * </ul>
   76    * <br>
   77    * If you put a String with key "contentType" object into the context within either your
   78    * servlet or within your template, then that will be used to override
   79    * the default content type specified in the properties file.
   80    *
   81    * @deprecated This servlet has been replaced by VelocityViewServlet,
   82    * available from the Velocity-Tools sub-project.  VelocityViewServlet
   83    * provides support for quick, clean MVC web development.
   84    * VelocityServlet will be removed in a future version of Velocity.
   85    *
   86    * @author Dave Bryson
   87    * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
   88    * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
   89    * @author <a href="kjohnson@transparent.com">Kent Johnson</a>
   90    * @author <a href="dlr@finemaltcoding.com">Daniel Rall</a>
   91    * $Id: VelocityServlet.java 463298 2006-10-12 16:10:32Z henning $
   92    */
   93   public abstract class VelocityServlet extends HttpServlet
   94   {
   95       /**
   96        * The context key for the HTTP request object.
   97        */
   98       public static final String REQUEST = "req";
   99   
  100       /**
  101        * The context key for the HTTP response object.
  102        */
  103       public static final String RESPONSE = "res";
  104   
  105       /**
  106        * The HTTP content type context key.
  107        */
  108       public static final String CONTENT_TYPE = "default.contentType";
  109   
  110       /**
  111        *  The default content type for the response
  112        */
  113       public static final String DEFAULT_CONTENT_TYPE = "text/html";
  114   
  115   
  116       /**
  117        *  Encoding for the output stream
  118        */
  119       public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1";
  120   
  121       /**
  122        * The default content type, itself defaulting to {@link
  123        * #DEFAULT_CONTENT_TYPE} if not configured.
  124        */
  125       private static String defaultContentType;
  126   
  127       /**
  128        * This is the string that is looked for when getInitParameter is
  129        * called (<code>org.apache.velocity.properties</code>).
  130        */
  131       protected static final String INIT_PROPS_KEY =
  132           "org.apache.velocity.properties";
  133   
  134       /**
  135        * Use of this properties key has been deprecated, and will be
  136        * removed in Velocity version 1.5.
  137        */
  138       private static final String OLD_INIT_PROPS_KEY = "properties";
  139   
  140       /**
  141        * Cache of writers
  142        */
  143   
  144       private static SimplePool writerPool = new SimplePool(40);
  145   
  146       /**
  147        * Performs initialization of this servlet.  Called by the servlet
  148        * container on loading.
  149        *
  150        * @param config The servlet configuration to apply.
  151        *
  152        * @exception ServletException
  153        */
  154       public void init( ServletConfig config )
  155           throws ServletException
  156       {
  157           super.init( config );
  158   
  159           /*
  160            *  do whatever we have to do to init Velocity
  161            */
  162           initVelocity( config );
  163   
  164           /*
  165            *  Now that Velocity is initialized, cache some config.
  166            */
  167           VelocityServlet.defaultContentType =
  168                   RuntimeSingleton.getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
  169       }
  170   
  171       /**
  172        *  Initializes the Velocity runtime, first calling
  173        *  loadConfiguration(ServletConvig) to get a
  174        *  java.util.Properties of configuration information
  175        *  and then calling Velocity.init().  Override this
  176        *  to do anything to the environment before the
  177        *  initialization of the singelton takes place, or to
  178        *  initialize the singleton in other ways.
  179        * @param config
  180        * @throws ServletException
  181        */
  182       protected void initVelocity( ServletConfig config )
  183            throws ServletException
  184       {
  185           try
  186           {
  187               /*
  188                *  call the overridable method to allow the
  189                *  derived classes a shot at altering the configuration
  190                *  before initializing Runtime
  191                */
  192   
  193               Properties props = loadConfiguration( config );
  194   
  195               Velocity.init( props );
  196           }
  197           catch( Exception e )
  198           {
  199               throw new ServletException("Error initializing Velocity: " + e, e);
  200           }
  201       }
  202   
  203       /**
  204        *  Loads the configuration information and returns that
  205        *  information as a Properties, which will be used to
  206        *  initialize the Velocity runtime.
  207        *  <br><br>
  208        *  Currently, this method gets the initialization parameter
  209        *  VelocityServlet.INIT_PROPS_KEY, which should be a file containing
  210        *  the configuration information.
  211        *  <br><br>
  212        *  To configure your Servlet Spec 2.2 compliant servlet runner to pass
  213        *  this to you, put the following in your WEB-INF/web.xml file
  214        *  <br>
  215        *  <pre>
  216        *    &lt;servlet&gt;
  217        *      &lt;servlet-name&gt; YourServlet &lt/servlet-name&gt;
  218        *      &lt;servlet-class&gt; your.package.YourServlet &lt;/servlet-class&gt;
  219        *      &lt;init-param&gt;
  220        *         &lt;param-name&gt; org.apache.velocity.properties &lt;/param-name&gt;
  221        *         &lt;param-value&gt; velocity.properties &lt;/param-value&gt;
  222        *      &lt;/init-param&gt;
  223        *    &lt;/servlet&gt;
  224        *   </pre>
  225        *
  226        * Alternately, if you wish to configure an entire context in this
  227        * fashion, you may use the following:
  228        *  <br>
  229        *  <pre>
  230        *    &lt;context-param&gt;
  231        *       &lt;param-name&gt; org.apache.velocity.properties &lt;/param-name&gt;
  232        *       &lt;param-value&gt; velocity.properties &lt;/param-value&gt;
  233        *       &lt;description&gt; Path to Velocity configuration &lt;/description&gt;
  234        *    &lt;/context-param&gt;
  235        *   </pre>
  236        *
  237        *  Derived classes may do the same, or take advantage of this code to do the loading for them via :
  238        *   <pre>
  239        *      Properties p = super.loadConfiguration( config );
  240        *   </pre>
  241        *  and then add or modify the configuration values from the file.
  242        *  <br>
  243        *
  244        *  @param config ServletConfig passed to the servlets init() function
  245        *                Can be used to access the real path via ServletContext (hint)
  246        *  @return java.util.Properties loaded with configuration values to be used
  247        *          to initialize the Velocity runtime.
  248        *  @throws FileNotFoundException if a specified file is not found.
  249        *  @throws IOException I/O problem accessing the specified file, if specified.
  250        * @deprecated Use VelocityViewServlet from the Velocity Tools
  251        * library instead.
  252        */
  253       protected Properties loadConfiguration(ServletConfig config)
  254           throws IOException, FileNotFoundException
  255       {
  256           // This is a little overly complex because of legacy support
  257           // for the initialization properties key "properties".
  258           // References to OLD_INIT_PROPS_KEY should be removed at
  259           // Velocity version 1.5.
  260           String propsFile = config.getInitParameter(INIT_PROPS_KEY);
  261           if (propsFile == null || propsFile.length() == 0)
  262           {
  263               ServletContext sc = config.getServletContext();
  264               propsFile = config.getInitParameter(OLD_INIT_PROPS_KEY);
  265               if (propsFile == null || propsFile.length() == 0)
  266               {
  267                   propsFile = sc.getInitParameter(INIT_PROPS_KEY);
  268                   if (propsFile == null || propsFile.length() == 0)
  269                   {
  270                       propsFile = sc.getInitParameter(OLD_INIT_PROPS_KEY);
  271                       if (propsFile != null && propsFile.length() > 0)
  272                       {
  273                           sc.log("Use of the properties initialization " +
  274                                  "parameter '" + OLD_INIT_PROPS_KEY + "' has " +
  275                                  "been deprecated by '" + INIT_PROPS_KEY + '\'');
  276                       }
  277                   }
  278               }
  279               else
  280               {
  281                   sc.log("Use of the properties initialization parameter '" +
  282                          OLD_INIT_PROPS_KEY + "' has been deprecated by '" +
  283                          INIT_PROPS_KEY + '\'');
  284               }
  285           }
  286   
  287           /*
  288            * This will attempt to find the location of the properties
  289            * file from the relative path to the WAR archive (ie:
  290            * docroot). Since JServ returns null for getRealPath()
  291            * because it was never implemented correctly, then we know we
  292            * will not have an issue with using it this way. I don't know
  293            * if this will break other servlet engines, but it probably
  294            * shouldn't since WAR files are the future anyways.
  295            */
  296   
  297           Properties p = new Properties();
  298   
  299           if ( propsFile != null )
  300           {
  301               p.load(getServletContext().getResourceAsStream(propsFile));
  302           }
  303   
  304           return p;
  305       }
  306   
  307       /**
  308        * Handles HTTP <code>GET</code> requests by calling {@link
  309        * #doRequest(HttpServletRequest, HttpServletResponse)}.
  310        * @param request
  311        * @param response
  312        * @throws ServletException
  313        * @throws IOException
  314        */
  315       public void doGet( HttpServletRequest request, HttpServletResponse response )
  316           throws ServletException, IOException
  317       {
  318           doRequest(request, response);
  319       }
  320   
  321       /**
  322        * Handles HTTP <code>POST</code> requests by calling {@link
  323        * #doRequest(HttpServletRequest, HttpServletResponse)}.
  324        * @param request
  325        * @param response
  326        * @throws ServletException
  327        * @throws IOException
  328        */
  329       public void doPost( HttpServletRequest request, HttpServletResponse response )
  330           throws ServletException, IOException
  331       {
  332           doRequest(request, response);
  333       }
  334   
  335       /**
  336        *  Handles all requests (by default).
  337        *
  338        *  @param request  HttpServletRequest object containing client request
  339        *  @param response HttpServletResponse object for the response
  340        * @throws ServletException
  341        * @throws IOException
  342        */
  343       protected void doRequest(HttpServletRequest request, HttpServletResponse response )
  344            throws ServletException, IOException
  345       {
  346           Context context = null;
  347           try
  348           {
  349               /*
  350                *  first, get a context
  351                */
  352   
  353               context = createContext( request, response );
  354   
  355               /*
  356                *   set the content type
  357                */
  358   
  359               setContentType( request, response );
  360   
  361               /*
  362                *  let someone handle the request
  363                */
  364   
  365               Template template = handleRequest( request, response, context );
  366               /*
  367                *  bail if we can't find the template
  368                */
  369   
  370               if ( template == null )
  371               {
  372                   return;
  373               }
  374   
  375               /*
  376                *  now merge it
  377                */
  378   
  379               mergeTemplate( template, context, response );
  380           }
  381           catch (Exception e)
  382           {
  383               /*
  384                *  call the error handler to let the derived class
  385                *  do something useful with this failure.
  386                */
  387   
  388               error( request, response, e);
  389           }
  390           finally
  391           {
  392               /*
  393                *  call cleanup routine to let a derived class do some cleanup
  394                */
  395   
  396               requestCleanup( request, response, context );
  397           }
  398       }
  399   
  400       /**
  401        *  A cleanup routine which is called at the end of the {@link
  402        *  #doRequest(HttpServletRequest, HttpServletResponse)}
  403        *  processing sequence, allowing a derived class to do resource
  404        *  cleanup or other end of process cycle tasks.
  405        *
  406        *  @param request servlet request from client
  407        *  @param response servlet reponse
  408        *  @param context  context created by the createContext() method
  409        */
  410       protected void requestCleanup( HttpServletRequest request, HttpServletResponse response, Context context )
  411       {
  412       }
  413   
  414       /**
  415        *  merges the template with the context.  Only override this if you really, really
  416        *  really need to. (And don't call us with questions if it breaks :)
  417        *
  418        *  @param template template object returned by the handleRequest() method
  419        *  @param context  context created by the createContext() method
  420        *  @param response servlet reponse (use this to get the output stream or Writer
  421        * @throws ResourceNotFoundException
  422        * @throws ParseErrorException
  423        * @throws MethodInvocationException
  424        * @throws IOException
  425        * @throws UnsupportedEncodingException
  426        * @throws Exception
  427        */
  428       protected void mergeTemplate( Template template, Context context, HttpServletResponse response )
  429           throws ResourceNotFoundException, ParseErrorException,
  430                  MethodInvocationException, IOException, UnsupportedEncodingException, Exception
  431       {
  432           ServletOutputStream output = response.getOutputStream();
  433           VelocityWriter vw = null;
  434           // ASSUMPTION: response.setContentType() has been called.
  435           String encoding = response.getCharacterEncoding();
  436   
  437           try
  438           {
  439               vw = (VelocityWriter) writerPool.get();
  440   
  441               if (vw == null)
  442               {
  443                   vw = new VelocityWriter(new OutputStreamWriter(output,
  444                                                                  encoding),
  445                                           4 * 1024, true);
  446               }
  447               else
  448               {
  449                   vw.recycle(new OutputStreamWriter(output, encoding));
  450               }
  451   
  452               template.merge(context, vw);
  453           }
  454           finally
  455           {
  456               if (vw != null)
  457               {
  458                   try
  459                   {
  460                       /*
  461                        *  flush and put back into the pool
  462                        *  don't close to allow us to play
  463                        *  nicely with others.
  464                        */
  465                       vw.flush();
  466                   }
  467                   catch (IOException e)
  468                   {
  469                       // do nothing
  470                   }
  471   
  472                   /*
  473                    * Clear the VelocityWriter's reference to its
  474                    * internal OutputStreamWriter to allow the latter
  475                    * to be GC'd while vw is pooled.
  476                    */
  477                   vw.recycle(null);
  478                   writerPool.put(vw);
  479               }
  480           }
  481       }
  482   
  483       /**
  484        * Sets the content type of the response, defaulting to {@link
  485        * #defaultContentType} if not overriden.  Delegates to {@link
  486        * #chooseCharacterEncoding(HttpServletRequest)} to select the
  487        * appropriate character encoding.
  488        *
  489        * @param request The servlet request from the client.
  490        * @param response The servlet reponse to the client.
  491        */
  492       protected void setContentType(HttpServletRequest request,
  493                                     HttpServletResponse response)
  494       {
  495           String contentType = VelocityServlet.defaultContentType;
  496           int index = contentType.lastIndexOf(';') + 1;
  497           if (index <= 0 || (index < contentType.length() &&
  498                              contentType.indexOf("charset", index) == -1))
  499           {
  500               // Append the character encoding which we'd like to use.
  501               String encoding = chooseCharacterEncoding(request);
  502               //RuntimeSingleton.debug("Chose output encoding of '" +
  503               //                       encoding + '\'');
  504               if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding))
  505               {
  506                   contentType += "; charset=" + encoding;
  507               }
  508           }
  509           response.setContentType(contentType);
  510           //RuntimeSingleton.debug("Response Content-Type set to '" +
  511           //                       contentType + '\'');
  512       }
  513   
  514       /**
  515        * Chooses the output character encoding to be used as the value
  516        * for the "charset=" portion of the HTTP Content-Type header (and
  517        * thus returned by <code>response.getCharacterEncoding()</code>).
  518        * Called by {@link #setContentType(HttpServletRequest,
  519        * HttpServletResponse)} if an encoding isn't already specified by
  520        * Content-Type.  By default, chooses the value of
  521        * RuntimeSingleton's <code>output.encoding</code> property.
  522        *
  523        * @param request The servlet request from the client.
  524        * @return The chosen character encoding.
  525        */
  526       protected String chooseCharacterEncoding(HttpServletRequest request)
  527       {
  528           return RuntimeSingleton.getString(RuntimeConstants.OUTPUT_ENCODING,
  529                                             DEFAULT_OUTPUT_ENCODING);
  530       }
  531   
  532       /**
  533        *  Returns a context suitable to pass to the handleRequest() method
  534        *  <br><br>
  535        *  Default implementation will create a VelocityContext object,
  536        *   put the HttpServletRequest and HttpServletResponse
  537        *  into the context accessable via the keys VelocityServlet.REQUEST and
  538        *  VelocityServlet.RESPONSE, respectively.
  539        *
  540        *  @param request servlet request from client
  541        *  @param response servlet reponse to client
  542        *
  543        *  @return context
  544        */
  545       protected Context createContext(HttpServletRequest request,  HttpServletResponse response )
  546       {
  547           /*
  548            *   create a new context
  549            */
  550   
  551           VelocityContext context = new VelocityContext();
  552   
  553           /*
  554            *   put the request/response objects into the context
  555            *   wrap the HttpServletRequest to solve the introspection
  556            *   problems
  557            */
  558   
  559           context.put( REQUEST,  request );
  560           context.put( RESPONSE, response );
  561   
  562           return context;
  563       }
  564   
  565       /**
  566        * Retrieves the requested template.
  567        *
  568        * @param name The file name of the template to retrieve relative to the
  569        *             template root.
  570        * @return     The requested template.
  571        * @throws ResourceNotFoundException if template not found
  572        *          from any available source.
  573        * @throws ParseErrorException if template cannot be parsed due
  574        *          to syntax (or other) error.
  575        * @throws Exception if an error occurs in template initialization
  576        */
  577       public Template getTemplate( String name )
  578           throws ResourceNotFoundException, ParseErrorException, Exception
  579       {
  580           return RuntimeSingleton.getTemplate(name);
  581       }
  582   
  583       /**
  584        * Retrieves the requested template with the specified
  585        * character encoding.
  586        *
  587        * @param name The file name of the template to retrieve relative to the
  588        *             template root.
  589        * @param encoding the character encoding of the template
  590        *
  591        * @return     The requested template.
  592        * @throws ResourceNotFoundException if template not found
  593        *          from any available source.
  594        * @throws ParseErrorException if template cannot be parsed due
  595        *          to syntax (or other) error.
  596        * @throws Exception if an error occurs in template initialization
  597        *
  598        *  @since Velocity v1.1
  599        */
  600       public Template getTemplate( String name, String encoding )
  601           throws ResourceNotFoundException, ParseErrorException, Exception
  602       {
  603           return RuntimeSingleton.getTemplate( name, encoding );
  604       }
  605   
  606       /**
  607        * Implement this method to add your application data to the context,
  608        * calling the <code>getTemplate()</code> method to produce your return
  609        * value.
  610        * <br><br>
  611        * In the event of a problem, you may handle the request directly
  612        * and return <code>null</code> or throw a more meaningful exception
  613        * for the error handler to catch.
  614        *
  615        *  @param request servlet request from client
  616        *  @param response servlet reponse
  617        *  @param ctx The context to add your data to.
  618        *  @return    The template to merge with your context or null, indicating
  619        *    that you handled the processing.
  620        * @throws Exception
  621        *
  622        *  @since Velocity v1.1
  623        */
  624       protected Template handleRequest( HttpServletRequest request, HttpServletResponse response, Context ctx )
  625           throws Exception
  626       {
  627           /*
  628            * invoke handleRequest
  629            */
  630   
  631           Template t =  handleRequest( ctx );
  632   
  633           /*
  634            *  if it returns null, this is the 'old' deprecated
  635            *  way, and we want to mimic the behavior for a little
  636            *  while anyway
  637            */
  638   
  639           if (t == null)
  640           {
  641               throw new Exception ("handleRequest(Context) returned null - no template selected!" );
  642           }
  643   
  644           return t;
  645       }
  646   
  647       /**
  648        * Implement this method to add your application data to the context,
  649        * calling the <code>getTemplate()</code> method to produce your return
  650        * value.
  651        * <br><br>
  652        * In the event of a problem, you may simple return <code>null</code>
  653        * or throw a more meaningful exception.
  654        *
  655        * @deprecated Use
  656        * {@link #handleRequest( HttpServletRequest request,
  657        * HttpServletResponse response, Context ctx )}
  658        *
  659        * @param ctx The context to add your data to.
  660        * @return    The template to merge with your context.
  661        * @throws Exception
  662        */
  663       protected Template handleRequest( Context ctx )
  664           throws Exception
  665       {
  666           throw new Exception ("You must override VelocityServlet.handleRequest( Context) "
  667                                + " or VelocityServlet.handleRequest( HttpServletRequest, "
  668                                + " HttpServletResponse, Context)" );
  669       }
  670   
  671       /**
  672        * Invoked when there is an error thrown in any part of doRequest() processing.
  673        * <br><br>
  674        * Default will send a simple HTML response indicating there was a problem.
  675        *
  676        * @param request original HttpServletRequest from servlet container.
  677        * @param response HttpServletResponse object from servlet container.
  678        * @param cause  Exception that was thrown by some other part of process.
  679        * @throws ServletException
  680        * @throws IOException
  681        */
  682       protected void error( HttpServletRequest request, HttpServletResponse response, Exception cause )
  683           throws ServletException, IOException
  684       {
  685           StringBuffer html = new StringBuffer();
  686           html.append("<html>");
  687           html.append("<title>Error</title>");
  688           html.append("<body bgcolor=\"#ffffff\">");
  689           html.append("<h2>VelocityServlet: Error processing the template</h2>");
  690           html.append("<pre>");
  691           String why = cause.getMessage();
  692           if (why != null && why.trim().length() > 0)
  693           {
  694               html.append(why);
  695               html.append("<br>");
  696           }
  697   
  698           StringWriter sw = new StringWriter();
  699           cause.printStackTrace( new PrintWriter( sw ) );
  700   
  701           html.append( sw.toString()  );
  702           html.append("</pre>");
  703           html.append("</body>");
  704           html.append("</html>");
  705           response.getOutputStream().print( html.toString() );
  706       }
  707   }

Save This Page
Home » velocity-1.5 » org.apache » velocity » servlet » [javadoc | source]