001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.jms.pool; 018 019import java.util.NoSuchElementException; 020import java.util.Properties; 021import java.util.concurrent.atomic.AtomicBoolean; 022import java.util.concurrent.atomic.AtomicReference; 023 024import javax.jms.Connection; 025import javax.jms.ConnectionFactory; 026import javax.jms.JMSException; 027import javax.jms.QueueConnection; 028import javax.jms.QueueConnectionFactory; 029import javax.jms.TopicConnection; 030import javax.jms.TopicConnectionFactory; 031 032import org.apache.commons.pool2.KeyedPooledObjectFactory; 033import org.apache.commons.pool2.PooledObject; 034import org.apache.commons.pool2.impl.DefaultPooledObject; 035import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 036import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * A JMS provider which pools Connection, Session and MessageProducer instances 042 * so it can be used with tools like <a href="http://camel.apache.org/activemq.html">Camel</a> and Spring's 043 * <a href="http://activemq.apache.org/spring-support.html">JmsTemplate and MessagListenerContainer</a>. 044 * Connections, sessions and producers are returned to a pool after use so that they can be reused later 045 * without having to undergo the cost of creating them again. 046 * 047 * b>NOTE:</b> while this implementation does allow the creation of a collection of active consumers, 048 * it does not 'pool' consumers. Pooling makes sense for connections, sessions and producers, which 049 * are expensive to create and can remain idle a minimal cost. Consumers, on the other hand, are usually 050 * just created at startup and left active, handling incoming messages as they come. When a consumer is 051 * complete, it is best to close it rather than return it to a pool for later reuse: this is because, 052 * even if a consumer is idle, ActiveMQ will keep delivering messages to the consumer's prefetch buffer, 053 * where they'll get held until the consumer is active again. 054 * 055 * If you are creating a collection of consumers (for example, for multi-threaded message consumption), you 056 * might want to consider using a lower prefetch value for each consumer (e.g. 10 or 20), to ensure that 057 * all messages don't end up going to just one of the consumers. See this FAQ entry for more detail: 058 * http://activemq.apache.org/i-do-not-receive-messages-in-my-second-consumer.html 059 * 060 * Optionally, one may configure the pool to examine and possibly evict objects as they sit idle in the 061 * pool. This is performed by an "idle object eviction" thread, which runs asynchronously. Caution should 062 * be used when configuring this optional feature. Eviction runs contend with client threads for access 063 * to objects in the pool, so if they run too frequently performance issues may result. The idle object 064 * eviction thread may be configured using the {@link org.apache.activemq.jms.pool.PooledConnectionFactory#setTimeBetweenExpirationCheckMillis} method. By 065 * default the value is -1 which means no eviction thread will be run. Set to a non-negative value to 066 * configure the idle eviction thread to run. 067 */ 068public class PooledConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory { 069 private static final transient Logger LOG = LoggerFactory.getLogger(PooledConnectionFactory.class); 070 071 protected final AtomicBoolean stopped = new AtomicBoolean(false); 072 private GenericKeyedObjectPool<ConnectionKey, ConnectionPool> connectionsPool; 073 074 protected Object connectionFactory; 075 076 private int maximumActiveSessionPerConnection = 500; 077 private int idleTimeout = 30 * 1000; 078 private int connectionTimeout = 30 * 1000; 079 private boolean blockIfSessionPoolIsFull = true; 080 private long blockIfSessionPoolIsFullTimeout = -1L; 081 private long expiryTimeout = 0l; 082 private boolean createConnectionOnStartup = true; 083 private boolean useAnonymousProducers = true; 084 private boolean reconnectOnException = true; 085 086 // Temporary value used to always fetch the result of makeObject. 087 private final AtomicReference<ConnectionPool> mostRecentlyCreated = new AtomicReference<ConnectionPool>(null); 088 089 public void initConnectionsPool() { 090 if (this.connectionsPool == null) { 091 final GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig(); 092 poolConfig.setJmxEnabled(false); 093 this.connectionsPool = new GenericKeyedObjectPool<ConnectionKey, ConnectionPool>( 094 new KeyedPooledObjectFactory<ConnectionKey, ConnectionPool>() { 095 @Override 096 public PooledObject<ConnectionPool> makeObject(ConnectionKey connectionKey) throws Exception { 097 Connection delegate = createConnection(connectionKey); 098 099 ConnectionPool connection = createConnectionPool(delegate); 100 connection.setIdleTimeout(getIdleTimeout()); 101 connection.setExpiryTimeout(getExpiryTimeout()); 102 connection.setMaximumActiveSessionPerConnection(getMaximumActiveSessionPerConnection()); 103 connection.setBlockIfSessionPoolIsFull(isBlockIfSessionPoolIsFull()); 104 if (isBlockIfSessionPoolIsFull() && getBlockIfSessionPoolIsFullTimeout() > 0) { 105 connection.setBlockIfSessionPoolIsFullTimeout(getBlockIfSessionPoolIsFullTimeout()); 106 } 107 connection.setUseAnonymousProducers(isUseAnonymousProducers()); 108 connection.setReconnectOnException(isReconnectOnException()); 109 110 LOG.trace("Created new connection: {}", connection); 111 112 PooledConnectionFactory.this.mostRecentlyCreated.set(connection); 113 114 return new DefaultPooledObject<ConnectionPool>(connection); 115 } 116 117 @Override 118 public void destroyObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 119 ConnectionPool connection = pooledObject.getObject(); 120 try { 121 LOG.trace("Destroying connection: {}", connection); 122 connection.close(); 123 } catch (Exception e) { 124 LOG.warn("Close connection failed for connection: " + connection + ". This exception will be ignored.",e); 125 } 126 } 127 128 @Override 129 public boolean validateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) { 130 ConnectionPool connection = pooledObject.getObject(); 131 if (connection != null && connection.expiredCheck()) { 132 LOG.trace("Connection has expired: {} and will be destroyed", connection); 133 return false; 134 } 135 136 return true; 137 } 138 139 @Override 140 public void activateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 141 } 142 143 @Override 144 public void passivateObject(ConnectionKey connectionKey, PooledObject<ConnectionPool> pooledObject) throws Exception { 145 } 146 147 }, poolConfig); 148 149 // Set max wait time to control borrow from pool. 150 this.connectionsPool.setMaxWaitMillis(getConnectionTimeout()); 151 152 // Set max idle (not max active) since our connections always idle in the pool. 153 this.connectionsPool.setMaxIdlePerKey(1); 154 this.connectionsPool.setLifo(false); 155 156 // We always want our validate method to control when idle objects are evicted. 157 this.connectionsPool.setTestOnBorrow(true); 158 this.connectionsPool.setTestWhileIdle(true); 159 } 160 } 161 162 /** 163 * @return the currently configured ConnectionFactory used to create the pooled Connections. 164 */ 165 public Object getConnectionFactory() { 166 return connectionFactory; 167 } 168 169 /** 170 * Sets the ConnectionFactory used to create new pooled Connections. 171 * <p/> 172 * Updates to this value do not affect Connections that were previously created and placed 173 * into the pool. In order to allocate new Connections based off this new ConnectionFactory 174 * it is first necessary to {@link #clear} the pooled Connections. 175 * 176 * @param toUse 177 * The factory to use to create pooled Connections. 178 */ 179 public void setConnectionFactory(final Object toUse) { 180 if (toUse instanceof ConnectionFactory) { 181 this.connectionFactory = toUse; 182 } else { 183 throw new IllegalArgumentException("connectionFactory should implement javax.jms.ConnectionFactory"); 184 } 185 } 186 187 @Override 188 public QueueConnection createQueueConnection() throws JMSException { 189 return (QueueConnection) createConnection(); 190 } 191 192 @Override 193 public QueueConnection createQueueConnection(String userName, String password) throws JMSException { 194 return (QueueConnection) createConnection(userName, password); 195 } 196 197 @Override 198 public TopicConnection createTopicConnection() throws JMSException { 199 return (TopicConnection) createConnection(); 200 } 201 202 @Override 203 public TopicConnection createTopicConnection(String userName, String password) throws JMSException { 204 return (TopicConnection) createConnection(userName, password); 205 } 206 207 @Override 208 public Connection createConnection() throws JMSException { 209 return createConnection(null, null); 210 } 211 212 @Override 213 public synchronized Connection createConnection(String userName, String password) throws JMSException { 214 if (stopped.get()) { 215 LOG.debug("PooledConnectionFactory is stopped, skip create new connection."); 216 return null; 217 } 218 219 ConnectionPool connection = null; 220 ConnectionKey key = new ConnectionKey(userName, password); 221 222 // This will either return an existing non-expired ConnectionPool or it 223 // will create a new one to meet the demand. 224 if (getConnectionsPool().getNumIdle(key) < getMaxConnections()) { 225 try { 226 connectionsPool.addObject(key); 227 connection = mostRecentlyCreated.getAndSet(null); 228 connection.incrementReferenceCount(); 229 } catch (Exception e) { 230 throw createJmsException("Error while attempting to add new Connection to the pool", e); 231 } 232 } else { 233 try { 234 // We can race against other threads returning the connection when there is an 235 // expiration or idle timeout. We keep pulling out ConnectionPool instances until 236 // we win and get a non-closed instance and then increment the reference count 237 // under lock to prevent another thread from triggering an expiration check and 238 // pulling the rug out from under us. 239 while (connection == null) { 240 try { 241 connection = connectionsPool.borrowObject(key); 242 } catch (NoSuchElementException ex) { 243 if (!"Timeout waiting for idle object".equals(ex.getMessage())) { 244 throw ex; 245 } 246 } 247 if (connection != null) { 248 synchronized (connection) { 249 if (connection.getConnection() != null) { 250 connection.incrementReferenceCount(); 251 break; 252 } 253 254 // Return the bad one to the pool and let if get destroyed as normal. 255 connectionsPool.returnObject(key, connection); 256 connection = null; 257 } 258 } 259 } 260 } catch (Exception e) { 261 throw createJmsException("Error while attempting to retrieve a connection from the pool", e); 262 } 263 264 try { 265 connectionsPool.returnObject(key, connection); 266 } catch (Exception e) { 267 throw createJmsException("Error when returning connection to the pool", e); 268 } 269 } 270 271 return newPooledConnection(connection); 272 } 273 274 protected Connection newPooledConnection(ConnectionPool connection) { 275 return new PooledConnection(connection); 276 } 277 278 private JMSException createJmsException(String msg, Exception cause) { 279 JMSException exception = new JMSException(msg); 280 exception.setLinkedException(cause); 281 exception.initCause(cause); 282 return exception; 283 } 284 285 protected Connection createConnection(ConnectionKey key) throws JMSException { 286 if (connectionFactory instanceof ConnectionFactory) { 287 if (key.getUserName() == null && key.getPassword() == null) { 288 return ((ConnectionFactory) connectionFactory).createConnection(); 289 } else { 290 return ((ConnectionFactory) connectionFactory).createConnection(key.getUserName(), key.getPassword()); 291 } 292 } else { 293 throw new IllegalStateException("connectionFactory should implement javax.jms.ConnectionFactory"); 294 } 295 } 296 297 public void start() { 298 LOG.debug("Staring the PooledConnectionFactory: create on start = {}", isCreateConnectionOnStartup()); 299 stopped.set(false); 300 if (isCreateConnectionOnStartup()) { 301 try { 302 // warm the pool by creating a connection during startup 303 createConnection().close(); 304 } catch (JMSException e) { 305 LOG.warn("Create pooled connection during start failed. This exception will be ignored.", e); 306 } 307 } 308 } 309 310 public void stop() { 311 if (stopped.compareAndSet(false, true)) { 312 LOG.debug("Stopping the PooledConnectionFactory, number of connections in cache: {}", 313 connectionsPool != null ? connectionsPool.getNumActive() : 0); 314 try { 315 if (connectionsPool != null) { 316 connectionsPool.close(); 317 connectionsPool = null; 318 } 319 } catch (Exception e) { 320 } 321 } 322 } 323 324 /** 325 * Clears all connections from the pool. Each connection that is currently in the pool is 326 * closed and removed from the pool. A new connection will be created on the next call to 327 * {@link #createConnection}. Care should be taken when using this method as Connections that 328 * are in use be client's will be closed. 329 */ 330 public void clear() { 331 if (stopped.get()) { 332 return; 333 } 334 335 getConnectionsPool().clear(); 336 } 337 338 /** 339 * Returns the currently configured maximum number of sessions a pooled Connection will 340 * create before it either blocks or throws an exception when a new session is requested, 341 * depending on configuration. 342 * 343 * @return the number of session instances that can be taken from a pooled connection. 344 */ 345 public int getMaximumActiveSessionPerConnection() { 346 return maximumActiveSessionPerConnection; 347 } 348 349 /** 350 * Sets the maximum number of active sessions per connection 351 * 352 * @param maximumActiveSessionPerConnection 353 * The maximum number of active session per connection in the pool. 354 */ 355 public void setMaximumActiveSessionPerConnection(int maximumActiveSessionPerConnection) { 356 this.maximumActiveSessionPerConnection = maximumActiveSessionPerConnection; 357 } 358 359 /** 360 * Controls the behavior of the internal session pool. By default the call to 361 * Connection.getSession() will block if the session pool is full. If the 362 * argument false is given, it will change the default behavior and instead the 363 * call to getSession() will throw a JMSException. 364 * 365 * The size of the session pool is controlled by the @see #maximumActive 366 * property. 367 * 368 * @param block - if true, the call to getSession() blocks if the pool is full 369 * until a session object is available. defaults to true. 370 */ 371 public void setBlockIfSessionPoolIsFull(boolean block) { 372 this.blockIfSessionPoolIsFull = block; 373 } 374 375 /** 376 * Returns whether a pooled Connection will enter a blocked state or will throw an Exception 377 * once the maximum number of sessions has been borrowed from the the Session Pool. 378 * 379 * @return true if the pooled Connection createSession method will block when the limit is hit. 380 * @see #setBlockIfSessionPoolIsFull(boolean) 381 */ 382 public boolean isBlockIfSessionPoolIsFull() { 383 return this.blockIfSessionPoolIsFull; 384 } 385 386 /** 387 * Returns the maximum number to pooled Connections that this factory will allow before it 388 * begins to return connections from the pool on calls to ({@link #createConnection}. 389 * 390 * @return the maxConnections that will be created for this pool. 391 */ 392 public int getMaxConnections() { 393 return getConnectionsPool().getMaxIdlePerKey(); 394 } 395 396 /** 397 * Sets the maximum number of pooled Connections (defaults to one). Each call to 398 * {@link #createConnection} will result in a new Connection being create up to the max 399 * connections value. 400 * 401 * @param maxConnections the maxConnections to set 402 */ 403 public void setMaxConnections(int maxConnections) { 404 getConnectionsPool().setMaxIdlePerKey(maxConnections); 405 getConnectionsPool().setMaxTotalPerKey(maxConnections); 406 } 407 408 /** 409 * Gets the Idle timeout value applied to new Connection's that are created by this pool. 410 * <p/> 411 * The idle timeout is used determine if a Connection instance has sat to long in the pool unused 412 * and if so is closed and removed from the pool. The default value is 30 seconds. 413 * 414 * @return idle timeout value (milliseconds) 415 */ 416 public int getIdleTimeout() { 417 return idleTimeout; 418 } 419 420 /** 421 * Sets the idle timeout value for Connection's that are created by this pool in Milliseconds, 422 * defaults to 30 seconds. 423 * <p/> 424 * For a Connection that is in the pool but has no current users the idle timeout determines how 425 * long the Connection can live before it is eligible for removal from the pool. Normally the 426 * connections are tested when an attempt to check one out occurs so a Connection instance can sit 427 * in the pool much longer than its idle timeout if connections are used infrequently. 428 * 429 * @param idleTimeout 430 * The maximum time a pooled Connection can sit unused before it is eligible for removal. 431 */ 432 public void setIdleTimeout(int idleTimeout) { 433 this.idleTimeout = idleTimeout; 434 } 435 436 /** 437 * Gets the connection timeout value. The maximum time waited to get a Connection from the pool. 438 * The default value is 30 seconds. 439 * 440 * @return connection timeout value (milliseconds) 441 */ 442 public int getConnectionTimeout() { 443 return connectionTimeout; 444 } 445 446 /** 447 * Sets the connection timeout value for getting Connections from this pool in Milliseconds, 448 * defaults to 30 seconds. 449 * 450 * @param connectionTimeout 451 * The maximum time to wait for getting a pooled Connection. 452 */ 453 public void setConnectionTimeout(int connectionTimeout) { 454 this.connectionTimeout = connectionTimeout; 455 } 456 457 /** 458 * allow connections to expire, irrespective of load or idle time. This is useful with failover 459 * to force a reconnect from the pool, to reestablish load balancing or use of the master post recovery 460 * 461 * @param expiryTimeout non zero in milliseconds 462 */ 463 public void setExpiryTimeout(long expiryTimeout) { 464 this.expiryTimeout = expiryTimeout; 465 } 466 467 /** 468 * @return the configured expiration timeout for connections in the pool. 469 */ 470 public long getExpiryTimeout() { 471 return expiryTimeout; 472 } 473 474 /** 475 * @return true if a Connection is created immediately on a call to {@link start}. 476 */ 477 public boolean isCreateConnectionOnStartup() { 478 return createConnectionOnStartup; 479 } 480 481 /** 482 * Whether to create a connection on starting this {@link PooledConnectionFactory}. 483 * <p/> 484 * This can be used to warm-up the pool on startup. Notice that any kind of exception 485 * happens during startup is logged at WARN level and ignored. 486 * 487 * @param createConnectionOnStartup <tt>true</tt> to create a connection on startup 488 */ 489 public void setCreateConnectionOnStartup(boolean createConnectionOnStartup) { 490 this.createConnectionOnStartup = createConnectionOnStartup; 491 } 492 493 /** 494 * Should Sessions use one anonymous producer for all producer requests or should a new 495 * MessageProducer be created for each request to create a producer object, default is true. 496 * 497 * When enabled the session only needs to allocate one MessageProducer for all requests and 498 * the MessageProducer#send(destination, message) method can be used. Normally this is the 499 * right thing to do however it does result in the Broker not showing the producers per 500 * destination. 501 * 502 * @return true if a PooledSession will use only a single anonymous message producer instance. 503 */ 504 public boolean isUseAnonymousProducers() { 505 return this.useAnonymousProducers; 506 } 507 508 /** 509 * Sets whether a PooledSession uses only one anonymous MessageProducer instance or creates 510 * a new MessageProducer for each call the create a MessageProducer. 511 * 512 * @param value 513 * Boolean value that configures whether anonymous producers are used. 514 */ 515 public void setUseAnonymousProducers(boolean value) { 516 this.useAnonymousProducers = value; 517 } 518 519 /** 520 * Gets the Pool of ConnectionPool instances which are keyed by different ConnectionKeys. 521 * 522 * @return this factories pool of ConnectionPool instances. 523 */ 524 protected GenericKeyedObjectPool<ConnectionKey, ConnectionPool> getConnectionsPool() { 525 initConnectionsPool(); 526 return this.connectionsPool; 527 } 528 529 /** 530 * Sets the number of milliseconds to sleep between runs of the idle Connection eviction thread. 531 * When non-positive, no idle object eviction thread will be run, and Connections will only be 532 * checked on borrow to determine if they have sat idle for too long or have failed for some 533 * other reason. 534 * <p/> 535 * By default this value is set to -1 and no expiration thread ever runs. 536 * 537 * @param timeBetweenExpirationCheckMillis 538 * The time to wait between runs of the idle Connection eviction thread. 539 */ 540 public void setTimeBetweenExpirationCheckMillis(long timeBetweenExpirationCheckMillis) { 541 getConnectionsPool().setTimeBetweenEvictionRunsMillis(timeBetweenExpirationCheckMillis); 542 } 543 544 /** 545 * @return the number of milliseconds to sleep between runs of the idle connection eviction thread. 546 */ 547 public long getTimeBetweenExpirationCheckMillis() { 548 return getConnectionsPool().getTimeBetweenEvictionRunsMillis(); 549 } 550 551 /** 552 * @return the number of Connections currently in the Pool 553 */ 554 public int getNumConnections() { 555 return getConnectionsPool().getNumIdle(); 556 } 557 558 /** 559 * Delegate that creates each instance of an ConnectionPool object. Subclasses can override 560 * this method to customize the type of connection pool returned. 561 * 562 * @param connection 563 * 564 * @return instance of a new ConnectionPool. 565 */ 566 protected ConnectionPool createConnectionPool(Connection connection) { 567 return new ConnectionPool(connection); 568 } 569 570 /** 571 * Returns the timeout to use for blocking creating new sessions 572 * 573 * @return true if the pooled Connection createSession method will block when the limit is hit. 574 * @see #setBlockIfSessionPoolIsFull(boolean) 575 */ 576 public long getBlockIfSessionPoolIsFullTimeout() { 577 return blockIfSessionPoolIsFullTimeout; 578 } 579 580 /** 581 * Controls the behavior of the internal session pool. By default the call to 582 * Connection.getSession() will block if the session pool is full. This setting 583 * will affect how long it blocks and throws an exception after the timeout. 584 * 585 * The size of the session pool is controlled by the @see #maximumActive 586 * property. 587 * 588 * Whether or not the call to create session blocks is controlled by the @see #blockIfSessionPoolIsFull 589 * property 590 * 591 * @param blockIfSessionPoolIsFullTimeout - if blockIfSessionPoolIsFullTimeout is true, 592 * then use this setting to configure how long to block before retry 593 */ 594 public void setBlockIfSessionPoolIsFullTimeout(long blockIfSessionPoolIsFullTimeout) { 595 this.blockIfSessionPoolIsFullTimeout = blockIfSessionPoolIsFullTimeout; 596 } 597 598 /** 599 * @return true if the underlying connection will be renewed on JMSException, false otherwise 600 */ 601 public boolean isReconnectOnException() { 602 return reconnectOnException; 603 } 604 605 /** 606 * Controls weather the underlying connection should be reset (and renewed) on JMSException 607 * 608 * @param reconnectOnException 609 * Boolean value that configures whether reconnect on exception should happen 610 */ 611 public void setReconnectOnException(boolean reconnectOnException) { 612 this.reconnectOnException = reconnectOnException; 613 } 614 615 /** 616 * Called by any superclass that implements a JNDIReferencable or similar that needs to collect 617 * the properties of this class for storage etc. 618 * 619 * This method should be updated any time there is a new property added. 620 * 621 * @param props 622 * a properties object that should be filled in with this objects property values. 623 */ 624 protected void populateProperties(Properties props) { 625 props.setProperty("maximumActiveSessionPerConnection", Integer.toString(getMaximumActiveSessionPerConnection())); 626 props.setProperty("maxConnections", Integer.toString(getMaxConnections())); 627 props.setProperty("idleTimeout", Integer.toString(getIdleTimeout())); 628 props.setProperty("expiryTimeout", Long.toString(getExpiryTimeout())); 629 props.setProperty("timeBetweenExpirationCheckMillis", Long.toString(getTimeBetweenExpirationCheckMillis())); 630 props.setProperty("createConnectionOnStartup", Boolean.toString(isCreateConnectionOnStartup())); 631 props.setProperty("useAnonymousProducers", Boolean.toString(isUseAnonymousProducers())); 632 props.setProperty("blockIfSessionPoolIsFullTimeout", Long.toString(getBlockIfSessionPoolIsFullTimeout())); 633 props.setProperty("reconnectOnException", Boolean.toString(isReconnectOnException())); 634 } 635}