Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » impl » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.impl;
   26   
   27   import java.io.Serializable;
   28   import java.sql.Connection;
   29   import java.util.Collections;
   30   import java.util.Iterator;
   31   import java.util.List;
   32   import java.util.Map;
   33   import java.util.Set;
   34   
   35   import org.slf4j.Logger;
   36   import org.slf4j.LoggerFactory;
   37   import org.hibernate.CacheMode;
   38   import org.hibernate.ConnectionReleaseMode;
   39   import org.hibernate.Criteria;
   40   import org.hibernate.EmptyInterceptor;
   41   import org.hibernate.EntityMode;
   42   import org.hibernate.FlushMode;
   43   import org.hibernate.HibernateException;
   44   import org.hibernate.Interceptor;
   45   import org.hibernate.LockMode;
   46   import org.hibernate.MappingException;
   47   import org.hibernate.ScrollMode;
   48   import org.hibernate.ScrollableResults;
   49   import org.hibernate.SessionException;
   50   import org.hibernate.StatelessSession;
   51   import org.hibernate.Transaction;
   52   import org.hibernate.UnresolvableObjectException;
   53   import org.hibernate.cache.CacheKey;
   54   import org.hibernate.collection.PersistentCollection;
   55   import org.hibernate.engine.EntityKey;
   56   import org.hibernate.engine.PersistenceContext;
   57   import org.hibernate.engine.QueryParameters;
   58   import org.hibernate.engine.StatefulPersistenceContext;
   59   import org.hibernate.engine.Versioning;
   60   import org.hibernate.engine.query.HQLQueryPlan;
   61   import org.hibernate.engine.query.NativeSQLQueryPlan;
   62   import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
   63   import org.hibernate.event.EventListeners;
   64   import org.hibernate.id.IdentifierGeneratorFactory;
   65   import org.hibernate.jdbc.Batcher;
   66   import org.hibernate.jdbc.JDBCContext;
   67   import org.hibernate.loader.criteria.CriteriaLoader;
   68   import org.hibernate.loader.custom.CustomLoader;
   69   import org.hibernate.loader.custom.CustomQuery;
   70   import org.hibernate.persister.entity.EntityPersister;
   71   import org.hibernate.persister.entity.OuterJoinLoadable;
   72   import org.hibernate.pretty.MessageHelper;
   73   import org.hibernate.proxy.HibernateProxy;
   74   import org.hibernate.type.Type;
   75   import org.hibernate.util.CollectionHelper;
   76   
   77   /**
   78    * @author Gavin King
   79    */
   80   public class StatelessSessionImpl extends AbstractSessionImpl
   81   		implements JDBCContext.Context, StatelessSession {
   82   
   83   	private static final Logger log = LoggerFactory.getLogger( StatelessSessionImpl.class );
   84   
   85   	private JDBCContext jdbcContext;
   86   	private PersistenceContext temporaryPersistenceContext = new StatefulPersistenceContext( this );
   87   
   88   	StatelessSessionImpl(Connection connection, SessionFactoryImpl factory) {
   89   		super( factory );
   90   		this.jdbcContext = new JDBCContext( this, connection, EmptyInterceptor.INSTANCE );
   91   	}
   92   
   93   
   94   	// inserts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   95   
   96   	public Serializable insert(Object entity) {
   97   		errorIfClosed();
   98   		return insert(null, entity);
   99   	}
  100   
  101   	public Serializable insert(String entityName, Object entity) {
  102   		errorIfClosed();
  103   		EntityPersister persister = getEntityPersister(entityName, entity);
  104   		Serializable id = persister.getIdentifierGenerator().generate(this, entity);
  105   		Object[] state = persister.getPropertyValues(entity, EntityMode.POJO);
  106   		if ( persister.isVersioned() ) {
  107   			boolean substitute = Versioning.seedVersion(state, persister.getVersionProperty(), persister.getVersionType(), this);
  108   			if ( substitute ) {
  109   				persister.setPropertyValues( entity, state, EntityMode.POJO );
  110   			}
  111   		}
  112   		if ( id == IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
  113   			id = persister.insert(state, entity, this);
  114   		}
  115   		else {
  116   			persister.insert(id, state, entity, this);
  117   		}
  118   		persister.setIdentifier(entity, id, EntityMode.POJO);
  119   		return id;
  120   	}
  121   
  122   
  123   	// deletes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  124   
  125   	public void delete(Object entity) {
  126   		errorIfClosed();
  127   		delete(null, entity);
  128   	}
  129   
  130   	public void delete(String entityName, Object entity) {
  131   		errorIfClosed();
  132   		EntityPersister persister = getEntityPersister(entityName, entity);
  133   		Serializable id = persister.getIdentifier(entity, EntityMode.POJO);
  134   		Object version = persister.getVersion(entity, EntityMode.POJO);
  135   		persister.delete(id, version, entity, this);
  136   	}
  137   
  138   
  139   	// updates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  140   
  141   	public void update(Object entity) {
  142   		errorIfClosed();
  143   		update(null, entity);
  144   	}
  145   
  146   	public void update(String entityName, Object entity) {
  147   		errorIfClosed();
  148   		EntityPersister persister = getEntityPersister(entityName, entity);
  149   		Serializable id = persister.getIdentifier(entity, EntityMode.POJO);
  150   		Object[] state = persister.getPropertyValues(entity, EntityMode.POJO);
  151   		Object oldVersion;
  152   		if ( persister.isVersioned() ) {
  153   			oldVersion = persister.getVersion(entity, EntityMode.POJO);
  154   			Object newVersion = Versioning.increment( oldVersion, persister.getVersionType(), this );
  155   			Versioning.setVersion(state, newVersion, persister);
  156   			persister.setPropertyValues(entity, state, EntityMode.POJO);
  157   		}
  158   		else {
  159   			oldVersion = null;
  160   		}
  161   		persister.update(id, state, null, false, null, oldVersion, entity, null, this);
  162   	}
  163   
  164   
  165   	// loading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  166   
  167   	public Object get(Class entityClass, Serializable id) {
  168   		return get( entityClass.getName(), id );
  169   	}
  170   
  171   	public Object get(Class entityClass, Serializable id, LockMode lockMode) {
  172   		return get( entityClass.getName(), id, lockMode );
  173   	}
  174   
  175   	public Object get(String entityName, Serializable id) {
  176   		return get(entityName, id, LockMode.NONE);
  177   	}
  178   
  179   	public Object get(String entityName, Serializable id, LockMode lockMode) {
  180   		errorIfClosed();
  181   		Object result = getFactory().getEntityPersister(entityName)
  182   				.load(id, null, lockMode, this);
  183   		temporaryPersistenceContext.clear();
  184   		return result;
  185   	}
  186   
  187   	public void refresh(Object entity) {
  188   		refresh( bestGuessEntityName( entity ), entity, LockMode.NONE );
  189   	}
  190   
  191   	public void refresh(String entityName, Object entity) {
  192   		refresh( entityName, entity, LockMode.NONE );
  193   	}
  194   
  195   	public void refresh(Object entity, LockMode lockMode) {
  196   		refresh( bestGuessEntityName( entity ), entity, lockMode );
  197   	}
  198   
  199   	public void refresh(String entityName, Object entity, LockMode lockMode) {
  200   		final EntityPersister persister = this.getEntityPersister( entityName, entity );
  201   		final Serializable id = persister.getIdentifier( entity, getEntityMode() );
  202   		if ( log.isTraceEnabled() ) {
  203   			log.trace(
  204   					"refreshing transient " +
  205   					MessageHelper.infoString( persister, id, this.getFactory() )
  206   			);
  207   		}
  208   		// TODO : can this ever happen???
  209   //		EntityKey key = new EntityKey( id, persister, source.getEntityMode() );
  210   //		if ( source.getPersistenceContext().getEntry( key ) != null ) {
  211   //			throw new PersistentObjectException(
  212   //					"attempted to refresh transient instance when persistent " +
  213   //					"instance was already associated with the Session: " +
  214   //					MessageHelper.infoString( persister, id, source.getFactory() )
  215   //			);
  216   //		}
  217   
  218   		if ( persister.hasCache() ) {
  219   			final CacheKey ck = new CacheKey(
  220   					id,
  221   			        persister.getIdentifierType(),
  222   			        persister.getRootEntityName(),
  223   			        this.getEntityMode(),
  224   			        this.getFactory()
  225   			);
  226   			persister.getCacheAccessStrategy().evict( ck );
  227   		}
  228   
  229   		String previousFetchProfile = this.getFetchProfile();
  230   		Object result = null;
  231   		try {
  232   			this.setFetchProfile( "refresh" );
  233   			result = persister.load( id, entity, lockMode, this );
  234   		}
  235   		finally {
  236   			this.setFetchProfile( previousFetchProfile );
  237   		}
  238   		UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() );
  239   	}
  240   
  241   	public Object immediateLoad(String entityName, Serializable id)
  242   			throws HibernateException {
  243   		throw new SessionException("proxies cannot be fetched by a stateless session");
  244   	}
  245   
  246   	public void initializeCollection(
  247   			PersistentCollection collection,
  248   	        boolean writing) throws HibernateException {
  249   		throw new SessionException("collections cannot be fetched by a stateless session");
  250   	}
  251   
  252   	public Object instantiate(
  253   			String entityName,
  254   	        Serializable id) throws HibernateException {
  255   		errorIfClosed();
  256   		return getFactory().getEntityPersister( entityName )
  257   				.instantiate( id, EntityMode.POJO );
  258   	}
  259   
  260   	public Object internalLoad(
  261   			String entityName,
  262   	        Serializable id,
  263   	        boolean eager,
  264   	        boolean nullable) throws HibernateException {
  265   		errorIfClosed();
  266   		EntityPersister persister = getFactory().getEntityPersister(entityName);
  267   		if ( !eager && persister.hasProxy() ) {
  268   			return persister.createProxy(id, this);
  269   		}
  270   		Object loaded = temporaryPersistenceContext.getEntity( new EntityKey(id, persister, EntityMode.POJO) );
  271   		//TODO: if not loaded, throw an exception
  272   		return loaded==null ? get( entityName, id ) : loaded;
  273   	}
  274   
  275   	public Iterator iterate(String query, QueryParameters queryParameters) throws HibernateException {
  276   		throw new UnsupportedOperationException();
  277   	}
  278   
  279   	public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters)
  280   	throws HibernateException {
  281   		throw new UnsupportedOperationException();
  282   	}
  283   
  284   	public List listFilter(Object collection, String filter, QueryParameters queryParameters)
  285   	throws HibernateException {
  286   		throw new UnsupportedOperationException();
  287   	}
  288   
  289   
  290   	public boolean isOpen() {
  291   		return !isClosed();
  292   	}
  293   
  294   	public void close() {
  295   		managedClose();
  296   	}
  297   
  298   	public ConnectionReleaseMode getConnectionReleaseMode() {
  299   		return factory.getSettings().getConnectionReleaseMode();
  300   	}
  301   
  302   	public boolean isAutoCloseSessionEnabled() {
  303   		return factory.getSettings().isAutoCloseSessionEnabled();
  304   	}
  305   
  306   	public boolean isFlushBeforeCompletionEnabled() {
  307   		return true;
  308   	}
  309   
  310   	public boolean isFlushModeNever() {
  311   		return false;
  312   	}
  313   
  314   	public void managedClose() {
  315   		if ( isClosed() ) {
  316   			throw new SessionException( "Session was already closed!" );
  317   		}
  318   		jdbcContext.getConnectionManager().close();
  319   		setClosed();
  320   	}
  321   
  322   	public void managedFlush() {
  323   		errorIfClosed();
  324   		getBatcher().executeBatch();
  325   	}
  326   
  327   	public boolean shouldAutoClose() {
  328   		return isAutoCloseSessionEnabled() && !isClosed();
  329   	}
  330   
  331   	public void afterTransactionCompletion(boolean successful, Transaction tx) {}
  332   
  333   	public void beforeTransactionCompletion(Transaction tx) {}
  334   
  335   	public String bestGuessEntityName(Object object) {
  336   		if (object instanceof HibernateProxy) {
  337   			object = ( (HibernateProxy) object ).getHibernateLazyInitializer().getImplementation();
  338   		}
  339   		return guessEntityName(object);
  340   	}
  341   
  342   	public Connection connection() {
  343   		errorIfClosed();
  344   		return jdbcContext.borrowConnection();
  345   	}
  346   
  347   	public int executeUpdate(String query, QueryParameters queryParameters)
  348   			throws HibernateException {
  349   		errorIfClosed();
  350   		queryParameters.validateParameters();
  351   		HQLQueryPlan plan = getHQLQueryPlan( query, false );
  352   		boolean success = false;
  353   		int result = 0;
  354   		try {
  355   			result = plan.performExecuteUpdate( queryParameters, this );
  356   			success = true;
  357   		}
  358   		finally {
  359   			afterOperation(success);
  360   		}
  361   		temporaryPersistenceContext.clear();
  362   		return result;
  363   	}
  364   
  365   	public Batcher getBatcher() {
  366   		errorIfClosed();
  367   		return jdbcContext.getConnectionManager()
  368   				.getBatcher();
  369   	}
  370   
  371   	public CacheMode getCacheMode() {
  372   		return CacheMode.IGNORE;
  373   	}
  374   
  375   	public int getDontFlushFromFind() {
  376   		return 0;
  377   	}
  378   
  379   	public Map getEnabledFilters() {
  380   		return CollectionHelper.EMPTY_MAP;
  381   	}
  382   
  383   	public Serializable getContextEntityIdentifier(Object object) {
  384   		errorIfClosed();
  385   		return null;
  386   	}
  387   
  388   	public EntityMode getEntityMode() {
  389   		return EntityMode.POJO;
  390   	}
  391   
  392   	public EntityPersister getEntityPersister(String entityName, Object object)
  393   			throws HibernateException {
  394   		errorIfClosed();
  395   		if ( entityName==null ) {
  396   			return factory.getEntityPersister( guessEntityName( object ) );
  397   		}
  398   		else {
  399   			return factory.getEntityPersister( entityName )
  400   					.getSubclassEntityPersister( object, getFactory(), EntityMode.POJO );
  401   		}
  402   	}
  403   
  404   	public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException {
  405   		errorIfClosed();
  406   		return null;
  407   	}
  408   
  409   	public Type getFilterParameterType(String filterParameterName) {
  410   		throw new UnsupportedOperationException();
  411   	}
  412   
  413   	public Object getFilterParameterValue(String filterParameterName) {
  414   		throw new UnsupportedOperationException();
  415   	}
  416   
  417   	public FlushMode getFlushMode() {
  418   		return FlushMode.COMMIT;
  419   	}
  420   
  421   	public Interceptor getInterceptor() {
  422   		return EmptyInterceptor.INSTANCE;
  423   	}
  424   
  425   	public EventListeners getListeners() {
  426   		throw new UnsupportedOperationException();
  427   	}
  428   
  429   	public PersistenceContext getPersistenceContext() {
  430   		return temporaryPersistenceContext;
  431   	}
  432   
  433   	public long getTimestamp() {
  434   		throw new UnsupportedOperationException();
  435   	}
  436   
  437   	public String guessEntityName(Object entity) throws HibernateException {
  438   		errorIfClosed();
  439   		return entity.getClass().getName();
  440   	}
  441   
  442   
  443   	public boolean isConnected() {
  444   		return jdbcContext.getConnectionManager().isCurrentlyConnected();
  445   	}
  446   
  447   	public boolean isTransactionInProgress() {
  448   		return jdbcContext.isTransactionInProgress();
  449   	}
  450   
  451   	public void setAutoClear(boolean enabled) {
  452   		throw new UnsupportedOperationException();
  453   	}
  454   
  455   	public void setCacheMode(CacheMode cm) {
  456   		throw new UnsupportedOperationException();
  457   	}
  458   
  459   	public void setFlushMode(FlushMode fm) {
  460   		throw new UnsupportedOperationException();
  461   	}
  462   
  463   	public Transaction getTransaction() throws HibernateException {
  464   		errorIfClosed();
  465   		return jdbcContext.getTransaction();
  466   	}
  467   
  468   	public Transaction beginTransaction() throws HibernateException {
  469   		errorIfClosed();
  470   		Transaction result = getTransaction();
  471   		result.begin();
  472   		return result;
  473   	}
  474   
  475   	public boolean isEventSource() {
  476   		return false;
  477   	}
  478   
  479   /////////////////////////////////////////////////////////////////////////////////////////////////////
  480   
  481   	//TODO: COPY/PASTE FROM SessionImpl, pull up!
  482   
  483   	public List list(String query, QueryParameters queryParameters) throws HibernateException {
  484   		errorIfClosed();
  485   		queryParameters.validateParameters();
  486   		HQLQueryPlan plan = getHQLQueryPlan( query, false );
  487   		boolean success = false;
  488   		List results = CollectionHelper.EMPTY_LIST;
  489   		try {
  490   			results = plan.performList( queryParameters, this );
  491   			success = true;
  492   		}
  493   		finally {
  494   			afterOperation(success);
  495   		}
  496   		temporaryPersistenceContext.clear();
  497   		return results;
  498   	}
  499   
  500   	public void afterOperation(boolean success) {
  501   		if ( !jdbcContext.isTransactionInProgress() ) {
  502   			jdbcContext.afterNontransactionalQuery(success);
  503   		}
  504   	}
  505   
  506   	public Criteria createCriteria(Class persistentClass, String alias) {
  507   		errorIfClosed();
  508   		return new CriteriaImpl( persistentClass.getName(), alias, this );
  509   	}
  510   
  511   	public Criteria createCriteria(String entityName, String alias) {
  512   		errorIfClosed();
  513   		return new CriteriaImpl(entityName, alias, this);
  514   	}
  515   
  516   	public Criteria createCriteria(Class persistentClass) {
  517   		errorIfClosed();
  518   		return new CriteriaImpl( persistentClass.getName(), this );
  519   	}
  520   
  521   	public Criteria createCriteria(String entityName) {
  522   		errorIfClosed();
  523   		return new CriteriaImpl(entityName, this);
  524   	}
  525   
  526   	public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode) {
  527   		errorIfClosed();
  528   		String entityName = criteria.getEntityOrClassName();
  529   		CriteriaLoader loader = new CriteriaLoader(
  530   				getOuterJoinLoadable(entityName),
  531   		        factory,
  532   		        criteria,
  533   		        entityName,
  534   		        getEnabledFilters()
  535   			);
  536   		return loader.scroll(this, scrollMode);
  537   	}
  538   
  539   	public List list(CriteriaImpl criteria) throws HibernateException {
  540   		errorIfClosed();
  541   		String[] implementors = factory.getImplementors( criteria.getEntityOrClassName() );
  542   		int size = implementors.length;
  543   
  544   		CriteriaLoader[] loaders = new CriteriaLoader[size];
  545   		for( int i=0; i <size; i++ ) {
  546   			loaders[i] = new CriteriaLoader(
  547   					getOuterJoinLoadable( implementors[i] ),
  548   			        factory,
  549   			        criteria,
  550   			        implementors[i],
  551   			        getEnabledFilters()
  552   			);
  553   		}
  554   
  555   
  556   		List results = Collections.EMPTY_LIST;
  557   		boolean success = false;
  558   		try {
  559   			for( int i=0; i<size; i++ ) {
  560   				final List currentResults = loaders[i].list(this);
  561   				currentResults.addAll(results);
  562   				results = currentResults;
  563   			}
  564   			success = true;
  565   		}
  566   		finally {
  567   			afterOperation(success);
  568   		}
  569   		temporaryPersistenceContext.clear();
  570   		return results;
  571   	}
  572   
  573   	private OuterJoinLoadable getOuterJoinLoadable(String entityName) throws MappingException {
  574   		EntityPersister persister = factory.getEntityPersister(entityName);
  575   		if ( !(persister instanceof OuterJoinLoadable) ) {
  576   			throw new MappingException( "class persister is not OuterJoinLoadable: " + entityName );
  577   		}
  578   		return ( OuterJoinLoadable ) persister;
  579   	}
  580   
  581   	public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters)
  582   	throws HibernateException {
  583   		errorIfClosed();
  584   		CustomLoader loader = new CustomLoader( customQuery, getFactory() );
  585   
  586   		boolean success = false;
  587   		List results;
  588   		try {
  589   			results = loader.list(this, queryParameters);
  590   			success = true;
  591   		}
  592   		finally {
  593   			afterOperation(success);
  594   		}
  595   		temporaryPersistenceContext.clear();
  596   		return results;
  597   	}
  598   
  599   	public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters)
  600   	throws HibernateException {
  601   		errorIfClosed();
  602   		CustomLoader loader = new CustomLoader( customQuery, getFactory() );
  603   		return loader.scroll(queryParameters, this);
  604   	}
  605   
  606   	public ScrollableResults scroll(String query, QueryParameters queryParameters) throws HibernateException {
  607   		errorIfClosed();
  608   		HQLQueryPlan plan = getHQLQueryPlan( query, false );
  609   		return plan.performScroll( queryParameters, this );
  610   	}
  611   
  612   	public void afterScrollOperation() {
  613   		temporaryPersistenceContext.clear();
  614   	}
  615   
  616   	public void flush() {}
  617   
  618   	public String getFetchProfile() {
  619   		return null;
  620   	}
  621   
  622   	public JDBCContext getJDBCContext() {
  623   		return jdbcContext;
  624   	}
  625   
  626   	public void setFetchProfile(String name) {}
  627   
  628   	public void afterTransactionBegin(Transaction tx) {}
  629   
  630   	protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException {
  631   		// no auto-flushing to support in stateless session
  632   		return false;
  633   	}
  634   	
  635   	public int executeNativeUpdate(NativeSQLQuerySpecification nativeSQLQuerySpecification,
  636   			QueryParameters queryParameters) throws HibernateException {
  637   		errorIfClosed();
  638   		queryParameters.validateParameters();
  639   		NativeSQLQueryPlan plan = getNativeSQLQueryPlan(nativeSQLQuerySpecification);
  640   
  641   		boolean success = false;
  642   		int result = 0;
  643   		try {
  644   			result = plan.performExecuteUpdate(queryParameters, this);
  645   			success = true;
  646   		} finally {
  647   			afterOperation(success);
  648   		}
  649   		temporaryPersistenceContext.clear();
  650   		return result;
  651   	}
  652   
  653   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » impl » [javadoc | source]