<?php
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * @package thrift.processor
 */

namespace Thrift;

use Thrift\Exception\TException;
use Thrift\Protocol\TProtocol;
use Thrift\Protocol\TMultiplexedProtocol;
use Thrift\Protocol\TProtocolDecorator;
use Thrift\Type\TMessageType;

/**
 * <code>TMultiplexedProcessor</code> is a Processor allowing
 * a single <code>TServer</code> to provide multiple services.
 *
 * <p>To do so, you instantiate the processor and then register additional
 * processors with it, as shown in the following example:</p>
 *
 * <blockquote><code>
 *     $processor = new TMultiplexedProcessor();
 *
 *     processor->registerProcessor(
 *         "Calculator",
 *         new \tutorial\CalculatorProcessor(new CalculatorHandler()));
 *
 *     processor->registerProcessor(
 *         "WeatherReport",
 *         new \tutorial\WeatherReportProcessor(new WeatherReportHandler()));
 *
 *     $processor->process($protocol, $protocol);
 * </code></blockquote>
 */

class TMultiplexedProcessor {
    private $serviceProcessorMap_;

    /**
     * 'Register' a service with this <code>TMultiplexedProcessor</code>.  This
     * allows us to broker requests to individual services by using the service
     * name to select them at request time.
     *
     * @param serviceName Name of a service, has to be identical to the name
     * declared in the Thrift IDL, e.g. "WeatherReport".
     * @param processor Implementation of a service, ususally referred to
     * as "handlers", e.g. WeatherReportHandler implementing WeatherReport.Iface.
     */
    public function registerProcessor($serviceName, $processor) {
        $this->serviceProcessorMap_[$serviceName] = $processor;
    }

    /**
     * This implementation of <code>process</code> performs the following steps:
     *
     * <ol>
     *     <li>Read the beginning of the message.</li>
     *     <li>Extract the service name from the message.</li>
     *     <li>Using the service name to locate the appropriate processor.</li>
     *     <li>Dispatch to the processor, with a decorated instance of TProtocol
     *         that allows readMessageBegin() to return the original Message.</li>
     * </ol>
     *
     * @throws TException If the message type is not CALL or ONEWAY, if
     * the service name was not found in the message, or if the service
     * name was not found in the service map.
     */
    public function process(TProtocol $input, TProtocol $output) {
        /*
            Use the actual underlying protocol (e.g. TBinaryProtocol) to read the
            message header. This pulls the message "off the wire", which we'll
            deal with at the end of this method.
        */
        $input->readMessageBegin($fname, $mtype, $rseqid);

        if ($mtype !== TMessageType::CALL && $mtype != TMessageType::ONEWAY) {
            throw new TException("This should not have happened!?");
        }

        // Extract the service name and the new Message name.
        if (strpos($fname, TMultiplexedProtocol::SEPARATOR) === false) {
            throw new TException("Service name not found in message name: {$fname}. Did you " .
                "forget to use a TMultiplexProtocol in your client?");
        }
        list($serviceName, $messageName) = explode(':', $fname, 2);
        if (!array_key_exists($serviceName, $this->serviceProcessorMap_)) {
            throw new TException("Service name not found: {$serviceName}.  Did you forget " .
                "to call registerProcessor()?");
        }

        // Dispatch processing to the stored processor
        $processor = $this->serviceProcessorMap_[$serviceName];
        return $processor->process(
            new StoredMessageProtocol($input, $messageName, $mtype, $rseqid), $output
        );
    }
}

/**
 *  Our goal was to work with any protocol. In order to do that, we needed
 *  to allow them to call readMessageBegin() and get the Message in exactly
 *  the standard format, without the service name prepended to the Message name.
 */
class StoredMessageProtocol extends TProtocolDecorator {
    private $fname_, $mtype_, $rseqid_;

    public function __construct(TProtocol $protocol, $fname, $mtype, $rseqid) {
        parent::__construct($protocol);
        $this->fname_  = $fname;
        $this->mtype_  = $mtype;
        $this->rseqid_ = $rseqid;
    }

    public function readMessageBegin(&$name, &$type, &$seqid) {
        $name  = $this->fname_;
        $type  = $this->mtype_;
        $seqid = $this->rseqid_;
    }
}
