InitialContext::injectProperties()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * \AppserverIo\Psr\Naming\InitialContext
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author     Tim Wagner <[email protected]>
15
 * @copyright  2015 TechDivision GmbH <[email protected]>
16
 * @license    http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link       https://github.com/appserver-io-psr/naming
18
 * @link       http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Psr\Naming;
22
23
use Phlexy\Lexer;
24
use Phlexy\LexerDataGenerator;
25
use Phlexy\LexerFactory\Stateless\UsingPregReplace;
26
use AppserverIo\Properties\Properties;
27
use AppserverIo\Properties\PropertiesInterface;
28
use AppserverIo\Psr\Servlet\SessionUtils;
29
use AppserverIo\Psr\Servlet\ServletRequestInterface;
30
use AppserverIo\Psr\Application\ApplicationInterface;
31
use AppserverIo\RemoteMethodInvocation\ConnectionInterface;
32
use AppserverIo\RemoteMethodInvocation\LocalConnectionFactory;
33
use AppserverIo\RemoteMethodInvocation\RemoteConnectionFactory;
34
35
/**
36
 * Initial context implementation to lookup enterprise beans.
37
 *
38
 * @author     Tim Wagner <[email protected]>
39
 * @copyright  2015 TechDivision GmbH <[email protected]>
40
 * @license    http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
41
 * @link       https://github.com/appserver-io-psr/naming
42
 * @link       http://www.appserver.io
43
 */
44
class InitialContext
45
{
46
47
    /**
48
     * Lexer constants.
49
     *
50
     * @var integer
51
     */
52
    const T_CLASS = 0;
53
    const T_COLON = 1;
54
    const T_SCHEME = 2;
55
    const T_APPLICATION_SCOPE = 3;
56
    const T_GLOBAL_SCOPE = 4;
57
    const T_SEPARATOR = 5;
58
    const T_INTERFACE = 6;
59
60
    /**
61
     * The configuration properties for this context.
62
     *
63
     * @var \AppserverIo\Properties\PropertiesInterface
64
     */
65
    protected $properties;
66
67
    /**
68
     * The lexer used to parse the JNDI style bean names.
69
     *
70
     * @param \Phlexy\Lexer $lexer the lexer instance
71
     */
72
    protected $lexer;
73
74
    /**
75
     * The application instance the context is bound to.
76
     *
77
     * @var \AppserverIo\Psr\Application\ApplicationInterface
78
     */
79
    protected $application;
80
81
    /**
82
     * The servlet request instance the context is bound to.
83
     *
84
     * @var \AppserverIo\Psr\Servlet\ServletRequestInterface
85
     */
86
    protected $servletRequest;
87
88
    /**
89
     * The default properties for the context configuration.
90
     *
91
     * @var array
92
     */
93
    protected $defaultProperties = array(
94
        'transport' => 'http',
95
        'scheme'    => 'php',
96
        'user'      => 'appserver',
97
        'pass'      => 'appserver.i0',
98
        'host'      => '127.0.0.1',
99
        'port'      => '8585',
100
        'scope'     => 'app',
101
        'indexFile' => 'index.pc',
102
        'interface' => EnterpriseBeanResourceIdentifier::LOCAL_INTERFACE
103
    );
104
105
    /**
106
     * Initialize the initial context with the values of the passed properties.
107
     *
108
     * @param \AppserverIo\Properties\PropertiesInterface $properties The configuration properties
109
     */
110 11
    public function __construct(PropertiesInterface $properties = null)
111
    {
112
113
        // initialize the default properties if no ones has been passed
114 11
        if ($properties == null) {
115
            // initialize the default properties
116 11
            $properties = new Properties();
117 11
            foreach ($this->defaultProperties as $key => $value) {
118 11
                $properties->setProperty($key, $value);
119 11
            }
120 11
        }
121
122
        // inject the properties
123 11
        $this->injectProperties($properties);
124
125
        // create a factory for the lexer we use to parse the JNDI style bean names
126 11
        $factory = new UsingPregReplace(new LexerDataGenerator);
127
128
        // create the lexer instance and inject it
129 11
        $this->injectLexer(
130 11
            $factory->createLexer(
131
                array(
132 11
                    'php'                      => InitialContext::T_SCHEME,
133 11
                    'global\/([a-zA-Z0-9_-]+)' => InitialContext::T_GLOBAL_SCOPE,
134 11
                    'app'                      => InitialContext::T_APPLICATION_SCOPE,
135 11
                    '\:'                       => InitialContext::T_COLON,
136 11
                    '\/'                       => InitialContext::T_SEPARATOR,
137 11
                    'local|remote'             => InitialContext::T_INTERFACE,
138
                    '\w+'                      => InitialContext::T_CLASS
139 11
                )
140 11
            )
141 11
        );
142 11
    }
143
144
    /**
145
     * The configuration properties for this context.
146
     *
147
     * @param \AppserverIo\Properties\PropertiesInterface $properties The configuration properties
148
     *
149
     * @return void
150
     */
151 11
    public function injectProperties(PropertiesInterface $properties)
152
    {
153 11
        $this->properties = $properties;
154 11
    }
155
156
    /**
157
     * The lexer used to parse the JNDI style bean names.
158
     *
159
     * @param \Phlexy\Lexer $lexer the lexer instance
160
     *
161
     * @return void
162
     */
163 11
    public function injectLexer(Lexer $lexer)
164
    {
165 11
        $this->lexer = $lexer;
166 11
    }
167
168
    /**
169
     * The application instance this context is bound to.
170
     *
171
     * @param \AppserverIo\Psr\Application\ApplicationInterface $application The application instance
172
     *
173
     * @return void
174
     */
175
    public function injectApplication(ApplicationInterface $application)
176
    {
177
        $this->application = $application;
178
    }
179
180
    /**
181
     * The servlet request instance for binding stateful session beans to.
182
     *
183
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface $servletRequest The servlet request instance
184
     *
185
     * @return void
186
     */
187
    public function injectServletRequest(ServletRequestInterface $servletRequest)
188
    {
189
        $this->servletRequest = $servletRequest;
190
    }
191
192
    /**
193
     * Returns the initial context configuration properties.
194
     *
195
     * @return \AppserverIo\Properties\PropertiesInterface The configuration properties
196
     */
197 11
    public function getProperties()
198
    {
199 11
        return $this->properties;
200
    }
201
202
    /**
203
     * Returns the lexer used to parse the JNDI style bean names.
204
     *
205
     * @return \Phlexy\Lexer The lexer instance
206
     */
207 11
    public function getLexer()
208
    {
209 11
        return $this->lexer;
210
    }
211
212
    /**
213
     * Returns the application instance this context is bound to.
214
     *
215
     * @return \AppserverIo\Psr\Application\ApplicationInterface The application instance
216
     */
217
    public function getApplication()
218
    {
219
        return $this->application;
220
    }
221
222
    /**
223
     * Returns the servlet request instance for binding stateful session beans to.
224
     *
225
     * @return \AppserverIo\Psr\Servlet\ServletRequestInterface The servlet request instance
226
     */
227
    public function getServletRequest()
228
    {
229
        return $this->servletRequest;
230
    }
231
232
    /**
233
     * Returns the requested enterprise bean instance remote/local.
234
     *
235
     * Example URL for loading a local instance of the UserProcessor session bean:
236
     *
237
     * php:app/UserProcessor/local
238
     *
239
     * @param string $name      The name of the requested enterprise bean
240
     * @param string $sessionId The session-ID, necessary for lookup stateful session beans
241
     *
242
     * @return object The requested enterprise bean instance
243
     *
244
     * @throws \AppserverIo\Psr\Naming\NamingException Is thrown if we can't lookup the enterprise bean with the passed identifier
245
     */
246
    public function lookup($name, $sessionId = null)
247
    {
248
249
        // at least we need a resource identifier
250
        if ($name == null) {
251
            throw new NamingException(sprintf('%s expects a valid resource identifier as parameter', __METHOD__));
252
        }
253
254
        // a valid resource identifier always has to be string
255
        if (is_string($name) === false) {
256
            throw new NamingException(sprintf('Invalid value %s for parameter \'name\' has been found, value MUST be a string', $name));
257
        }
258
259
        // prepare the resource identifier for the requested enterprise bean
260
        $resourceIdentifier = $this->prepareResourceIdentifier($name);
261
262
        // This MUST be a remote lookup to another application and the passed name MUST be a complete URL!
263
        if ($resourceIdentifier->isRemote()) {
264
            return $this->doRemoteLookup($resourceIdentifier, $sessionId);
265
        }
266
267
        // This MUST be a local lookup to this application, the passed name CAN either be the bean class name
268
        // only or the complete path including application name and script file name => index.pc for example!
269
        if ($resourceIdentifier->isLocal()) {
270
            return $this->doLocalLookup($resourceIdentifier, $sessionId);
271
        }
272
273
        // throw an exception if we can't lookup the bean
274
        throw new NamingException(sprintf('Can\'t lookup enterprise bean with passed identifier %s', $name));
275
    }
276
277
    /**
278
     * Prepares a new resource identifier instance from the passed resource name, that has to be
279
     * a valid URL.
280
     *
281
     * @param string $resourceName The URL with the resource information
282
     *
283
     * @return \AppserverIo\Psr\Naming\EnterpriseBeanResourceIdentifier The initialized resource identifier
284
     *
285
     * @throws \AppserverIo\Psr\Naming\NamingException Is thrown if we can't find the necessary application context
286
     */
287 11
    public function prepareResourceIdentifier($resourceName)
288
    {
289
290
        // load the URL properties
291 11
        $properties = clone $this->getProperties();
292
293
        // lex the passed resource name
294 11
        foreach ($this->getLexer()->lex($resourceName) as $token) {
295 11
            switch ($token[0]) { // check the found type
296
297 11
                case InitialContext::T_SCHEME: // we found a scheme, e. g. php
298 8
                    $properties->setProperty('scheme', $token[2]);
299 8
                    break;
300
301 11
                case InitialContext::T_INTERFACE: // we found a interface, e. g. local
302 8
                    $properties->setProperty('interface', $token[2]);
303 8
                    break;
304
305 11
                case InitialContext::T_CLASS: // we found the class name, e. g. MyProcessor
306 11
                    $properties->setProperty('className', $token[2]);
307 11
                    break;
308
309 10
                case InitialContext::T_GLOBAL_SCOPE: // we found the scope, e. g. app or global
310 5
                    $properties->setProperty('contextName', current($token[3]));
311 5
                    break;
312
313 10
                default: // do nothing with the other tokens : and /
314 10
                    break;
315 11
            }
316 11
        }
317
318
        // initialize the resource identifier from the passed resource
319 11
        return EnterpriseBeanResourceIdentifier::createFromProperties($properties);
320
    }
321
322
    /**
323
     * Makes a remote lookup for the URL containing the information of the requested bean.
324
     *
325
     * @param \AppserverIo\Psr\Naming\ResourceIdentifier $resourceIdentifier The resource identifier with the requested bean information
326
     * @param string                                     $sessionId          The session-ID, necessary for lookup stateful session beans
327
     *
328
     * @return object The been proxy instance
329
     */
330
    protected function doRemoteLookup(ResourceIdentifier $resourceIdentifier, $sessionId = null)
331
    {
332
333
        // initialize the connection
334
        $connection = RemoteConnectionFactory::createContextConnection();
335
        $connection->injectPort($resourceIdentifier->getPort());
336
        $connection->injectAddress($resourceIdentifier->getHost());
337
        $connection->injectTransport($resourceIdentifier->getTransport());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AppserverIo\Psr\Naming\ResourceIdentifier as the method getTransport() does only exist in the following sub-classes of AppserverIo\Psr\Naming\ResourceIdentifier: AppserverIo\Psr\Naming\E...eBeanResourceIdentifier. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
338
339
        // query if we've a context name defined in the resource identifier
340
        if ($contextName = $resourceIdentifier->getContextName()) {
341
            // if yes, use it as application name
342
            $connection->injectAppName($contextName);
343 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344
            // use the application context from the servlet request
345
            if ($this->getServletRequest() && $this->getServletRequest()->getContext()) {
0 ignored issues
show
Bug introduced by
The method getContext() does not seem to exist on object<AppserverIo\Psr\S...ervletRequestInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
346
                $application = $this->getServletRequest()->getContext();
0 ignored issues
show
Bug introduced by
The method getContext() does not seem to exist on object<AppserverIo\Psr\S...ervletRequestInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
            } else {
348
                $application = $this->getApplication();
349
            }
350
351
            // use the application name
352
            $connection->injectAppName($application->getName());
353
        }
354
355
        // finally try to lookup the bean
356
        return $this->doLookup($resourceIdentifier, $connection, $sessionId);
357
    }
358
359
    /**
360
     * Makes a local lookup for the bean with the passed class name.
361
     *
362
     * @param \AppserverIo\Psr\Naming\ResourceIdentifier $resourceIdentifier The resource identifier with the requested bean information
363
     * @param string                                     $sessionId          The session-ID, necessary for lookup stateful session beans
364
     *
365
     * @return object The bean proxy instance
366
     */
367
    protected function doLocalLookup(ResourceIdentifier $resourceIdentifier, $sessionId = null)
368
    {
369
370
        // use the application context from the servlet request
371 View Code Duplication
        if ($this->getServletRequest() && $this->getServletRequest()->getContext()) {
0 ignored issues
show
Bug introduced by
The method getContext() does not seem to exist on object<AppserverIo\Psr\S...ervletRequestInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
            $application = $this->getServletRequest()->getContext();
0 ignored issues
show
Bug introduced by
The method getContext() does not seem to exist on object<AppserverIo\Psr\S...ervletRequestInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
373
        } else {
374
            $application = $this->getApplication();
375
        }
376
377
        // initialize the connection
378
        $connection = LocalConnectionFactory::createContextConnection();
379
        $connection->injectApplication($application);
380
381
        // finally try to lookup the bean
382
        return $this->doLookup($resourceIdentifier, $connection, $sessionId);
383
    }
384
385
    /**
386
     * Finally this method does the lookup for the passed resource identifier
387
     * using the also passed connection.
388
     *
389
     * @param \AppserverIo\Psr\Naming\ResourceIdentifier              $resourceIdentifier The identifier for the requested bean
390
     * @param \AppserverIo\RemoteMethodInvocation\ConnectionInterface $connection         The connection we use for loading the bean
391
     * @param string                                                  $sessionId          The session-ID, necessary for lookup stateful session beans
392
     *
393
     * @return object The been proxy instance
394
     */
395
    protected function doLookup(ResourceIdentifier $resourceIdentifier, ConnectionInterface $connection, $sessionId = null)
396
    {
397
398
        try {
399
            // initialize the context session
400
            $session = $connection->createContextSession();
401
402
            // check if we've a HTTP session ID passed
403
            if ($sessionId == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $sessionId of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
404
                // simulate a unique session ID
405
                $session->setSessionId(SessionUtils::generateRandomString());
406
            } else {
407
                // else, set the passed session ID
408
                $session->setSessionId($sessionId);
409
            }
410
411
            // check if we've a HTTP servlet request that may contain a session
412
            if ($servletRequest = $this->getServletRequest()) {
413
                $session->injectServletRequest($servletRequest);
414
            }
415
416
            // load the proxy class name from the resource identifier => that is the path information
417
            $proxyClass = $this->getApplication()->search(sprintf('%s/proxy', $className = $resourceIdentifier->getClassName()), array($sessionId));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AppserverIo\Psr\Naming\ResourceIdentifier as the method getClassName() does only exist in the following sub-classes of AppserverIo\Psr\Naming\ResourceIdentifier: AppserverIo\Psr\Naming\E...eBeanResourceIdentifier. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
The method search() does not seem to exist on object<AppserverIo\Psr\A...n\ApplicationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
418
419
            // instanciate the proxy
420
            $proxyInstance = new $proxyClass($className);
421
            $proxyInstance->__setSession($session);
422
423
            // return the initialized proxy instance
424
            return $proxyInstance;
425
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
426
        } catch (\Exception $e) {
427
            // log and re-throw the exception
428
            $this->getApplication()->getNamingDirectory()->search('php:global/log/System')->error($e->__toString());
0 ignored issues
show
Bug introduced by
The method getNamingDirectory() does not seem to exist on object<AppserverIo\Psr\A...n\ApplicationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
429
            throw $e;
430
        }
431
    }
432
}
433