Aude has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/179904

Change subject: Update Wikidata, update site links ui and improve page view 
performance
......................................................................

Update Wikidata, update site links ui and improve page view performance

Change-Id: I41d67d9561ea197bcf8d14f63c566972c83c4ab4
---
M composer.lock
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimgrouplistview.js
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
M extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
M extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.sitelinklistview.js
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
M 
extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
M extensions/Wikibase/repo/Wikibase.hooks.php
D extensions/Wikibase/repo/includes/ContentRetriever.php
M extensions/Wikibase/repo/includes/EntityParserOutputGenerator.php
M extensions/Wikibase/repo/includes/actions/EditEntityAction.php
M extensions/Wikibase/repo/includes/actions/ViewEntityAction.php
A extensions/Wikibase/repo/includes/content/DeferredCopyEntityHolder.php
A extensions/Wikibase/repo/includes/content/DeferredDecodingEntityHolder.php
M extensions/Wikibase/repo/includes/content/EntityContent.php
M extensions/Wikibase/repo/includes/content/EntityContentFactory.php
M extensions/Wikibase/repo/includes/content/EntityHandler.php
A extensions/Wikibase/repo/includes/content/EntityHolder.php
A extensions/Wikibase/repo/includes/content/EntityInstanceHolder.php
M extensions/Wikibase/repo/includes/content/ItemContent.php
M extensions/Wikibase/repo/includes/content/PropertyContent.php
D extensions/Wikibase/repo/tests/phpunit/includes/ContentRetrieverTest.php
M 
extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorTest.php
M 
extensions/Wikibase/repo/tests/phpunit/includes/actions/ViewEntityActionTest.php
A 
extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredCopyEntityHolderTest.php
A 
extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredDecodingEntityHolderTest.php
M extensions/Wikibase/repo/tests/phpunit/includes/content/EntityHandlerTest.php
A 
extensions/Wikibase/repo/tests/phpunit/includes/content/EntityInstanceHolderTest.php
M extensions/Wikibase/repo/tests/phpunit/includes/content/ItemHandlerTest.php
M vendor/composer/autoload_classmap.php
M vendor/composer/installed.json
33 files changed, 898 insertions(+), 549 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikidata 
refs/changes/04/179904/1

diff --git a/composer.lock b/composer.lock
index a804a0a..7daac87 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1233,7 +1233,7 @@
             "source": {
                 "type": "git",
                 "url": 
"https://git.wikimedia.org/git/mediawiki/extensions/Wikibase.git";,
-                "reference": "3a65287aaeb3bf6109c8e7ad557100b04f19c47a"
+                "reference": "563b0cd967edf27fa2d9cd972b03bf4f902c8e06"
             },
             "require": {
                 "data-values/common": "~0.2.0",
@@ -1302,7 +1302,7 @@
                 "issues": "https://bugzilla.wikimedia.org/";,
                 "irc": "irc://irc.freenode.net/wikidata"
             },
-            "time": "2014-12-10 10:40:44"
+            "time": "2014-12-15 12:01:25"
         },
         {
             "name": "wikibase/wikimedia-badges",
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimgrouplistview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimgrouplistview.js
index f661279..8f4e372 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimgrouplistview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimgrouplistview.js
@@ -233,18 +233,21 @@
        },
 
        /**
-        * Triggers entering a new claimlistview to the claimgrouplistview. 
This involves triggering
-        * the corresponding process for the new pending claimlistview by 
triggering the claimlistview's
-        * enterNewItem() method that instantiates a pending claimview to be 
added to the pending
-        * claimview which itself is added to the claimgrouplistview.
-        * @since 0.5
+        * Triggers adding a new `claimlistview` to the `claimgrouplistview`. 
This involves triggering
+        * the corresponding process for the new pending `claimlistview` by 
triggering the
+        * `claimlistview`'s `enterNewItem()` method that instantiates a 
pending `claimview` to be added
+        * to the pending `claimlistview` which itself is added to the 
`claimgrouplistview`.
+        * @see jQuery.wikibase.listview.enterNewItem
+        *
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$claimlistview
         */
        enterNewItem: function() {
                var self = this,
                        lia = this.listview().listItemAdapter();
 
-               this.element
-               .one( 'listviewenternewitem.' + this.widgetName, function( 
event, $claimlistview ) {
+               return this.listview().enterNewItem().done( function( 
$claimlistview ) {
                        var afterStopEditingEvent = lia.prefixedEvent( 
'afterstopediting.' + self.widgetName );
 
                        $claimlistview
@@ -280,13 +283,10 @@
                                                new wb.datamodel.StatementList( 
newStatements )
                                        ) );
                                }
-
                        } );
 
                        lia.liInstance( $claimlistview ).enterNewItem();
                } );
-
-               this.listview().enterNewItem();
        },
 
        /**
@@ -402,25 +402,11 @@
                                        return;
                                }
 
-                               $claimgrouplistview.one(
-                                       'listviewenternewitem',
-                                       function( event, $claimlistview ) {
-                                               if( event.target === 
claimgrouplistview.$listview[0] ) {
-                                                       $claimlistview.one(
-                                                               // TODO: There 
should be a "claimlistviewafterstartediting" event.
-                                                               
'claimviewafterstartediting.addtoolbar '
-                                                               + 
'statementviewafterstartediting.addtoolbar',
-                                                               function() {
-                                                                       var 
listview = claimgrouplistview.$listview.data( 'listview' ),
-                                                                               
lia = listview.listItemAdapter();
-                                                                       
lia.liInstance( $claimlistview ).focus();
-                                                               }
-                                                       );
-                                               }
-                                       }
-                               );
-
-                               claimgrouplistview.enterNewItem();
+                               claimgrouplistview.enterNewItem().done( 
function( $claimlistview ) {
+                                       var claimlistview = 
$claimlistview.data( 'claimlistview' ),
+                                               listview = 
claimlistview.$listview.data( 'listview' );
+                                       listview.listItemAdapter().liInstance( 
listview.items() ).focus();
+                               } );
 
                                toolbarcontroller.registerEventHandler(
                                        event.data.toolbar.type,
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
index aec6af1..f4f5244 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
@@ -320,16 +320,19 @@
        },
 
        /**
-        * Will insert a new list member into the list. The new list member 
will be a widget of the type
-        * displayed in the list, but without value, so the user can specify a 
value.
-        * @since 0.4
+        * Adds a new, pending `statementview` to the `claimlistview`.
+        * @see jQuery.wikibase.listview.enterNewItem
+        *
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$statementview
         */
        enterNewItem: function() {
                var self = this,
                        lia = this.listview().listItemAdapter(),
                        afterStopEditingEvent = lia.prefixedEvent( 
'afterstopediting.' + self.widgetName );
 
-               this.element.one( 'listviewenternewitem', function( event, 
$statementview ) {
+               return this.listview().enterNewItem().done( function( 
$statementview ) {
                        var statementview = lia.liInstance( $statementview );
 
                        $statementview
@@ -348,8 +351,6 @@
 
                        statementview.startEditing();
                } );
-
-               this.listview().enterNewItem();
        },
 
        /**
@@ -441,21 +442,17 @@
                                        return;
                                }
 
-                               $claimlistview.one( 'listviewenternewitem', 
function( event, $view ) {
-                                       if( event.target === 
claimlistview.$listview[0] ) {
-                                               $view.one(
-                                                       
'claimviewafterstartediting.addtoolbar '
-                                                       + 
'statementviewafterstartediting.addtoolbar',
-                                                       function() {
-                                                               var listview = 
claimlistview.$listview.data( 'listview' ),
-                                                                       lia = 
listview.listItemAdapter();
-                                                               lia.liInstance( 
$view ).focus();
-                                                       }
-                                               );
-                                       }
+                               claimlistview.enterNewItem().done( function( 
$view ) {
+                                       $view.one(
+                                               
'claimviewafterstartediting.addtoolbar '
+                                               + 
'statementviewafterstartediting.addtoolbar',
+                                               function() {
+                                                       var listview = 
claimlistview.$listview.data( 'listview' ),
+                                                               lia = 
listview.listItemAdapter();
+                                                       lia.liInstance( $view 
).focus();
+                                               }
+                                       );
                                } );
-
-                               claimlistview.enterNewItem();
 
                                // Re-focus "add" button after having added or 
having cancelled adding a statement:
                                var eventName = 
'claimlistviewafterstopediting.addtoolbar';
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
index f72936e..6f1bc70 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
@@ -741,8 +741,14 @@
                                } )
                                .off( '.addtoolbar' )
                                .on( 'addtoolbaradd.addtoolbar', function( e ) {
-                                       listview.enterNewItem();
-                                       
listview.value()[listview.value().length - 1].enterNewItem();
+                                       listview.enterNewItem().done( function( 
$snaklistview ) {
+                                               var snaklistview = 
listview.listItemAdapter().liInstance( $snaklistview );
+                                               
snaklistview.enterNewItem().done( function( $snakview ) {
+                                                       var 
snaklistviewListview = snaklistview.$listview.data( 'listview' ),
+                                                               lia = 
snaklistviewListview.listItemAdapter();
+                                                       lia.liInstance( 
$snakview ).focus();
+                                               } );
+                                       } );
                                } );
 
                                toolbarController.registerEventHandler(
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.listview.js 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
index 5b4a0cb..c3a4c95 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
@@ -390,14 +390,18 @@
        } ),
 
        /**
-        * Will insert a new list member into the list. The new list member 
will be a Widget of the type
-        * displayed in the list, but without value, so the user can specify a 
value.
+        * Inserts a new list item into the list. The new list item will be a 
widget instance of the
+        * type set on the list, but without any value.
         *
-        * @since 0.4
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$newLi The new list item node. Use
+        *         `listItemAdapter().liInstance( $newLi )` to receive the 
widget instance.
         */
        enterNewItem: function() {
                var $newLi = this.addItem();
-               this._trigger( 'enternewitem', null, [ $newLi ] );
+               this._trigger( 'enternewitem', null, [$newLi] );
+               return $.Deferred().resolve( $newLi ).promise();
        },
 
        /**
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
index 948566a..f0a5616 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
@@ -172,13 +172,6 @@
                // FIXME: Rather fix the event bindings
                this._listview.widgetEventPrefix = 'referenceview';
 
-               // Whenever entering a new referenceview item, a single 
snaklistview needs to be created
-               // along. This creates a snakview ready to edit.
-               this.element
-               .on( 'referenceviewenternewitem.' + this.widgetName, function( 
event, $newLi ) {
-                       self.options.listItemAdapter.liInstance( $newLi 
).enterNewItem();
-               } );
-
                this._updateReferenceHashClass( this.value() );
        },
 
@@ -524,19 +517,28 @@
        },
 
        /**
-        * Initialize entering a new item to the referenceview.
+        * Adds a pending `snaklistview` to the referenceview.
+        * @see jQuery.wikibase.listview.enterNewItem
         * @since 0.5
         *
-        * @triggers change
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$snaklistview
         */
        enterNewItem: function() {
+               var self = this;
+
                this.startEditing();
 
-               this._listview.enterNewItem();
-
-               // Since the new snakview will be initialized empty which 
invalidates the snaklistview,
-               // external components using the snaklistview will be noticed 
via the "change" event.
-               this._trigger( 'change' );
+               return this._listview.enterNewItem().done( function( 
$snaklistview ) {
+                       self.options.listItemAdapter.liInstance( $snaklistview 
).enterNewItem()
+                       .done( function() {
+                               // Since the new snakview will be initialized 
empty which invalidates the
+                               // snaklistview, external components using the 
snaklistview will be noticed via
+                               // the "change" event.
+                               self._trigger( 'change' );
+                       } );
+               } );
        },
 
        /**
@@ -563,7 +565,7 @@
         * Sets/removes error state from the widget.
         * @since 0.4
         *
-        * @param {wkibase.api.RepoApiError} [error]
+        * @param {wikibase.api.RepoApiError} [error]
         */
        setError: function( error ) {
                if ( error ) {
@@ -608,13 +610,18 @@
        selector: '.wb-statement-references .wb-referenceview',
        events: {
                referenceviewstartediting: function( event, toolbarController ) 
{
-                       var $referenceview = $( event.target );
+                       var $referenceview = $( event.target ),
+                               referenceview = $referenceview.data( 
'referenceview' ),
+                               lia = referenceview.$listview.data( 'listview' 
).listItemAdapter();
 
                        $referenceview.addtoolbar( {
                                $container: $( '<div/>' ).appendTo( 
$referenceview )
                        } )
                        .on( 'addtoolbaradd.addtoolbar', function() {
-                               $referenceview.data( 'referenceview' 
).enterNewItem();
+                               $referenceview.data( 'referenceview' 
).enterNewItem()
+                                       .done( function( $snaklistview ) {
+                                               lia.liInstance( $snaklistview 
).focus();
+                                       } );
                        } );
 
                        toolbarController.registerEventHandler(
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.sitelinklistview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.sitelinklistview.js
index ede4ee2..0f876d2 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.sitelinklistview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.sitelinklistview.js
@@ -755,7 +755,12 @@
        },
 
        /**
-        * Triggers entering a new item offering respective input elements.
+        * Adds a pending `sitelinkview` to the `sitelinklistview`.
+        * @see jQuery.wikibase.listview.enterNewItem
+        *
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$sitelinkview
         */
        enterNewItem: function() {
                var self = this,
@@ -763,7 +768,7 @@
                        lia = listview.listItemAdapter(),
                        afterStopEditingEvent = lia.prefixedEvent( 
'afterstopediting.' + this.widgetName );
 
-               this.element.one( 'listviewenternewitem', function( event, 
$sitelinkview ) {
+               return listview.enterNewItem().done( function( $sitelinkview ) {
                        var sitelinkview = lia.liInstance( $sitelinkview );
 
                        $sitelinkview
@@ -793,11 +798,9 @@
                        } else {
                                sitelinkview.startEditing();
                        }
+
+                       this.__pendingItems = this.__pendingItems ? 
this.__pendingItems + 1 : 1;
                } );
-
-               listview.enterNewItem();
-
-               this.__pendingItems = this.__pendingItems ? this.__pendingItems 
+ 1 : 1;
        }
 } );
 
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
index 6916eab..f9fc515 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
@@ -421,15 +421,22 @@
        /**
         * Will insert a new list member into the list. The new list member 
will be a widget of the type
         * displayed in the list, but without value, so the user can specify a 
value.
-        * @since 0.4
+        * @see jQuery.wikibase.listview.enterNewItem
+        *
+        * @return {Object} jQuery.Promise
+        * @return {Function} return.done
+        * @return {jQuery} return.done.$snakview
         */
        enterNewItem: function() {
-               this._listview.addItem();
+               var $snakview = this._listview.addItem();
+
                this.startEditing();
 
                // Since the new snakview will be initialized empty which 
invalidates the snaklistview,
                // external components using the snaklistview will be noticed 
via the "change" event.
                this._trigger( 'change' );
+
+               return $.Deferred().resolve( $snakview ).promise();
        },
 
        /**
diff --git 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
index 9db8017..e509325 100644
--- 
a/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
+++ 
b/extensions/Wikibase/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
@@ -727,7 +727,10 @@
                                        return;
                                }
 
-                               listview.enterNewItem();
+                               listview.enterNewItem().done( function( 
$referenceview ) {
+                                       var referenceview = lia.liInstance( 
$referenceview );
+                                       referenceview.focus();
+                               } );
 
                                // Re-focus "add" button after having added or 
having cancelled adding a reference:
                                var eventName = lia.prefixedEvent( 
'afterstopediting.addtoolbar' );
diff --git a/extensions/Wikibase/repo/Wikibase.hooks.php 
b/extensions/Wikibase/repo/Wikibase.hooks.php
index 33a63fe..fceee04 100644
--- a/extensions/Wikibase/repo/Wikibase.hooks.php
+++ b/extensions/Wikibase/repo/Wikibase.hooks.php
@@ -962,6 +962,16 @@
                        $out->setProperty( 'wikibase-view-chunks', 
$placeholders );
                }
 
+               // used in ViewEntityAction and EditEntityAction to override 
the page html title
+               // with the label, if available, or else the id. Passed via 
parser output
+               // and output page to save overhead of fetching content and 
accessing an entity
+               // on page view.
+               $titleText = $parserOutput->getExtensionData( 
'wikibase-titletext' );
+
+               if ( $titleText ) {
+                       $out->setProperty( 'wikibase-titletext', $titleText );
+               }
+
                return true;
        }
 
diff --git a/extensions/Wikibase/repo/includes/ContentRetriever.php 
b/extensions/Wikibase/repo/includes/ContentRetriever.php
deleted file mode 100644
index 75bbbd1..0000000
--- a/extensions/Wikibase/repo/includes/ContentRetriever.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-namespace Wikibase;
-
-use Article;
-use Content;
-use Revision;
-use WebRequest;
-
-/**
- * Fetches content for a given Title / Article and request (diff or not diff)
- *
- * @since 0.5
- *
- * @todo put/merge this into core, with revision id / content fetching stuff
- * factored out of DifferenceEngine and Article classes. :)
- *
- * @licence GNU GPL v2+
- * @author Katie Filbert < [email protected] >
- * @author Thiemo Mättig
- */
-class ContentRetriever {
-
-       /**
-        * Returns the content to display on a page, given request params.
-        *
-        * If it is a diff request, then display the revision specified
-        * in the 'diff=' request param.
-        *
-        * @todo split out the get revision id stuff, add tests and see if
-        * any core code can be shared here
-        *
-        * @param WebRequest $request
-        * @param Article $article
-        *
-        * @return Content|null
-        */
-       public function getContentForRequest( WebRequest $request, Article 
$article ) {
-               $revision = $article->getRevisionFetched();
-
-               // check for delete or unavailable revision
-               if ( !$revision ) {
-                       return null;
-               }
-
-               if ( $request->getCheck( 'diff' ) ) {
-                       $oldId = $revision->getId();
-                       $diffValue = $request->getVal( 'diff' );
-                       $revision = $this->resolveDiffRevision( $oldId, 
$diffValue, $article );
-               }
-
-               return $revision !== null ?
-                       $revision->getContent( Revision::FOR_THIS_USER ) : null;
-       }
-
-       /**
-        * Get the revision specified in the diff parameter or prev/next 
revision of oldid
-        *
-        * @since 0.5
-        *
-        * @param int $oldId
-        * @param int|string $diffValue
-        * @param Article $article
-        *
-        * @return Revision|null
-        */
-       protected function resolveDiffRevision( $oldId, $diffValue, Article 
$article )
-       {
-               $newid = $this->resolveNewid( $oldId, $diffValue, $article );
-
-               if ( !$newid ) {
-                       $newid = $article->getTitle()->getLatestRevID();
-               }
-
-               return Revision::newFromId( $newid );
-       }
-
-       /**
-        * Get the revision ID specified in the diff parameter or prev/next 
revision of oldid
-        *
-        * @since 0.5
-        *
-        * @param int $oldId
-        * @param int|string $diffValue
-        * @param Article $article
-        *
-        * @return Bool|int
-        */
-       protected function resolveNewid( $oldId, $diffValue, Article $article )
-       {
-               $contentHandler = 
$article->getRevisionFetched()->getContentHandler();
-               $context = $article->getContext();
-               $differenceEngine = $contentHandler->createDifferenceEngine( 
$context, $oldId, $diffValue );
-               return $differenceEngine->getNewid();
-       }
-
-}
diff --git a/extensions/Wikibase/repo/includes/EntityParserOutputGenerator.php 
b/extensions/Wikibase/repo/includes/EntityParserOutputGenerator.php
index 31f1e44..cf389f5 100644
--- a/extensions/Wikibase/repo/includes/EntityParserOutputGenerator.php
+++ b/extensions/Wikibase/repo/includes/EntityParserOutputGenerator.php
@@ -2,8 +2,9 @@
 
 namespace Wikibase;
 
+use OutOfBoundsException;
 use ParserOutput;
-use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\SiteLinkList;
 use Wikibase\DataModel\StatementListProvider;
@@ -125,6 +126,8 @@
                        $this->addBadgesToParserOutput( $parserOutput, 
$entity->getSiteLinkList() );
                }
 
+               $this->addTitleTextToParserOutput( $parserOutput, $entity );
+
                if ( $generateHtml ) {
                        $this->addHtmlToParserOutput(
                                $parserOutput,
@@ -246,6 +249,28 @@
 
        /**
         * @param ParserOutput $parserOutput
+        * @param Entity $entity
+        */
+       private function addTitleTextToParserOutput( ParserOutput 
$parserOutput, Entity $entity ) {
+               $preferred = 
$this->languageFallbackChain->extractPreferredValue( $entity->getLabels() );
+
+               if ( is_array( $preferred ) ) {
+                       $titleText = $preferred['value'];
+               } else {
+                       $entityId = $entity->getId();
+
+                       if ( !$entityId ) {
+                               return;
+                       }
+
+                       $titleText = $entityId->getSerialization();
+               }
+
+               $parserOutput->setExtensionData( 'wikibase-titletext', 
$titleText );
+       }
+
+       /**
+        * @param ParserOutput $parserOutput
         * @param EntityRevision $entityRevision
         * @param array $entityInfo obtained from 
EntityInfoBuilder::getEntityInfo
         * @param boolean $editable
diff --git a/extensions/Wikibase/repo/includes/actions/EditEntityAction.php 
b/extensions/Wikibase/repo/includes/actions/EditEntityAction.php
index c613acc..e4970e5 100644
--- a/extensions/Wikibase/repo/includes/actions/EditEntityAction.php
+++ b/extensions/Wikibase/repo/includes/actions/EditEntityAction.php
@@ -285,11 +285,10 @@
 
                $restore = $req->getCheck( 'restore' );
 
-               //$this->getOutput()->setContext( $this->getContext() ); //XXX: 
WTF?
                $this->getOutput()->setPageTitle(
                        $this->msg(
                                $restore ? 'wikibase-restore-title' : 
'wikibase-undo-title',
-                               $this->getLabelText( $latestContent ),
+                               $this->getTitleText(),
                                $olderRevision->getId(),
                                $newerRevision->getId()
                        )
@@ -344,26 +343,19 @@
        }
 
        /**
-        * Returns the label that should be shown to represent the given entity.
-        *
-        * @param EntityContent $content
+        * Used for overriding the page html title with the label, if 
available, or else the id.
+        * This is passed via parser output and output page to save overhead on 
view / edit actions.
         *
         * @return string
         */
-       private function getLabelText( EntityContent $content ) {
-               $labelData = null;
+       private function getTitleText() {
+               $titleText = $this->getOutput()->getProperty( 
'wikibase-titletext' );
 
-               // TODO: use a message like <autoredircomment> to represent the 
redirect.
-               if ( !$content->isRedirect() ) {
-                       $languageFallbackChain = 
$this->getLanguageFallbackChain();
-                       $labelData = 
$languageFallbackChain->extractPreferredValueOrAny( 
$content->getEntity()->getLabels() );
+               if ( $titleText === null ) {
+                       $titleText = '';
                }
 
-               if ( $labelData ) {
-                       return $labelData['value'];
-               } else {
-                       return $this->getPageTitle();
-               }
+               return $titleText;
        }
 
        /**
diff --git a/extensions/Wikibase/repo/includes/actions/ViewEntityAction.php 
b/extensions/Wikibase/repo/includes/actions/ViewEntityAction.php
index a747a01..4e36631 100644
--- a/extensions/Wikibase/repo/includes/actions/ViewEntityAction.php
+++ b/extensions/Wikibase/repo/includes/actions/ViewEntityAction.php
@@ -23,39 +23,6 @@
 abstract class ViewEntityAction extends ViewAction {
 
        /**
-        * @var LanguageFallbackChain
-        */
-       protected $languageFallbackChain;
-
-       /**
-        * Get the language fallback chain.
-        * Uses the default WikibaseRepo instance to get the service if it was 
not previously set.
-        *
-        * @since 0.4
-        *
-        * @return LanguageFallbackChain
-        */
-       public function getLanguageFallbackChain() {
-               if ( $this->languageFallbackChain === null ) {
-                       $this->languageFallbackChain = 
WikibaseRepo::getDefaultInstance()->getLanguageFallbackChainFactory()
-                               ->newFromContext( $this->getContext() );
-               }
-
-               return $this->languageFallbackChain;
-       }
-
-       /**
-        * Set language fallback chain.
-        *
-        * @since 0.4
-        *
-        * @param LanguageFallbackChain $chain
-        */
-       public function setLanguageFallbackChain( LanguageFallbackChain $chain 
) {
-               $this->languageFallbackChain = $chain;
-       }
-
-       /**
         * @see Action::getName()
         *
         * @since 0.1
@@ -82,29 +49,16 @@
         *
         * @since 0.1
         *
-        * TODO: permissing checks?
         * Parent is doing $this->checkCanExecute( $this->getUser() )
         */
        public function show() {
                if ( !$this->getArticle()->getPage()->exists() ) {
+                       // @fixme could use ShowMissingArticle hook instead.
+                       // Article checks for missing / deleted revisions and 
either
+                       // shows appropriate error page or deleted revision, if 
permission allows.
                        $this->displayMissingEntity();
                } else {
-                       $contentRetriever = new ContentRetriever();
-                       $content = $contentRetriever->getContentForRequest(
-                               $this->getRequest(),
-                               $this->getArticle()
-                       );
-
-                       if ( !( $content instanceof EntityContent ) ) {
-                               $this->getOutput()->showErrorPage(
-                                               
'wikibase-entity-not-viewable-title',
-                                               'wikibase-entity-not-viewable',
-                                               $content->getModel()
-                               );
-                               return;
-                       }
-
-                       $this->displayEntityContent( $content );
+                       $this->showEntityPage();
                }
        }
 
@@ -126,13 +80,11 @@
        }
 
        /**
-        * Displays the entity content.
+        * Displays the entity page.
         *
         * @since 0.1
-        *
-        * @param EntityContent $content
         */
-       private function displayEntityContent( EntityContent $content ) {
+       private function showEntityPage() {
                $outputPage = $this->getOutput();
 
                $editable = $this->isEditable();
@@ -146,21 +98,26 @@
                $this->getArticle()->setParserOptions( $parserOptions );
                $this->getArticle()->view();
 
-               $this->applyLabelToTitleText( $outputPage, $content );
+               $this->overrideTitleText( $outputPage );
        }
 
        /**
+        * This will be the label, if available, or else the entity id (e.g. 
'Q42').
+        * This is passed via parser output and output page to save overhead on 
view actions.
+        *
         * @param OutputPage $outputPage
-        * @param EntityContent $content
         */
-       private function applyLabelToTitleText( OutputPage $outputPage, 
EntityContent $content ) {
-               // Figure out which label to use for title.
-               $labelText = $this->getLabelText( $content );
+       private function overrideTitleText( OutputPage $outputPage ) {
+               $titleText = $this->getOutput()->getProperty( 
'wikibase-titletext' );
+
+               if ( $titleText === null ) {
+                       return;
+               }
 
                if ( $this->isDiff() ) {
-                       $this->setPageTitle( $outputPage, $labelText );
+                       $this->setPageTitle( $outputPage, $titleText );
                } else {
-                       $this->setHTMLTitle( $outputPage, $labelText );
+                       $this->setHTMLTitle( $outputPage, $titleText );
                }
        }
 
@@ -191,28 +148,6 @@
        private function setHTMLTitle( OutputPage $outputPage, $labelText ) {
                // Prevent replacing {{...}} by using rawParams() instead of 
params():
                $outputPage->setHTMLTitle( $this->msg( 'pagetitle' 
)->rawParams( $labelText ) );
-       }
-
-       /**
-        * @param EntityContent $content
-        *
-        * @return string
-        */
-       private function getLabelText( EntityContent $content ) {
-               // Figure out which label to use for title.
-               $languageFallbackChain = $this->getLanguageFallbackChain();
-               $labelData = null;
-
-               if ( !$content->isRedirect() ) {
-                       $labels = $content->getEntity()->getLabels();
-                       $labelData = 
$languageFallbackChain->extractPreferredValueOrAny( $labels );
-               }
-
-               if ( $labelData ) {
-                       return $labelData['value'];
-               } else {
-                       return $content->getEntityId()->getSerialization();
-               }
        }
 
        /**
diff --git 
a/extensions/Wikibase/repo/includes/content/DeferredCopyEntityHolder.php 
b/extensions/Wikibase/repo/includes/content/DeferredCopyEntityHolder.php
new file mode 100644
index 0000000..b1fca44
--- /dev/null
+++ b/extensions/Wikibase/repo/includes/content/DeferredCopyEntityHolder.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Wikibase\Content;
+
+use RuntimeException;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\EntityId;
+
+/**
+ * EntityHolder implementing deferred copying.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class DeferredCopyEntityHolder implements EntityHolder {
+
+       /**
+        * @var EntityHolder
+        */
+       private $entityHolder;
+
+       /**
+        * @var Entity|null
+        */
+       private $entity = null;
+
+       /**
+        * @param EntityHolder $entityHolder
+        */
+       public function __construct( EntityHolder $entityHolder ) {
+               $this->entityHolder = $entityHolder;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * This implements lazy initialization of the entity: when called for 
the first time,
+        * this method will call getEntity() on the EntityHolder passed to the 
constructor,
+        * and then calls copy() on the entity returned. The resulting copy is 
returned.
+        * Subsequent calls will return the same entity.
+        *
+        * @param string $expectedClass The class with which the result is 
expected to be compatible.
+        * Defaults to Entity.
+        *
+        * @throws RuntimeException If the entity held by this EntityHolder is 
not compatible with $expectedClass.
+        * @return Entity
+        */
+       public function getEntity( $expectedClass = 
'Wikibase\DataModel\Entity\Entity' ) {
+               if ( !$this->entity ) {
+                       $entity = $this->entityHolder->getEntity( 
$expectedClass );
+                       $this->entity = $entity->copy();
+               }
+
+               return $this->entity;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * @return EntityId|null
+        */
+       public function getEntityId() {
+               return $this->entityHolder->getEntityId();
+       }
+
+       /**
+        * @see EntityHolder::getEntityType
+        *
+        * @return string
+        */
+       public function getEntityType() {
+               return $this->entityHolder->getEntityType();
+       }
+
+}
diff --git 
a/extensions/Wikibase/repo/includes/content/DeferredDecodingEntityHolder.php 
b/extensions/Wikibase/repo/includes/content/DeferredDecodingEntityHolder.php
new file mode 100644
index 0000000..46562b9
--- /dev/null
+++ b/extensions/Wikibase/repo/includes/content/DeferredDecodingEntityHolder.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Wikibase\Content;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\Lib\Store\EntityContentDataCodec;
+
+/**
+ * EntityHolder implementing deferred deserialization.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class DeferredDecodingEntityHolder implements EntityHolder {
+
+       /**
+        * @var Entity|null
+        */
+       private $entity = null;
+
+       /**
+        * @var EntityContentDataCodec
+        */
+       private $codec;
+
+       /**
+        * @var string
+        */
+       private $blob;
+
+       /**
+        * @var string
+        */
+       private $format;
+
+       /**
+        * @var string
+        */
+       private $entityType;
+
+       /**
+        * @var EntityId
+        */
+       private $entityId;
+
+       /**
+        * @param EntityContentDataCodec $codec
+        * @param string $blob
+        * @param string $format
+        * @param $entityType
+        * @param EntityId $entityId
+        *
+        * @throws InvalidArgumentException
+        */
+       public function __construct( EntityContentDataCodec $codec, $blob, 
$format, $entityType, EntityId $entityId = null ) {
+               if ( !is_string( $blob ) ) {
+                       throw new InvalidArgumentException( '$blob must be a 
string' );
+               }
+
+               if ( !is_string( $format ) ) {
+                       throw new InvalidArgumentException( '$format must be a 
string' );
+               }
+
+               if ( !is_string( $entityType ) ) {
+                       throw new InvalidArgumentException( '$entityType must 
be a string' );
+               }
+
+               $this->codec = $codec;
+               $this->blob = $blob;
+               $this->format = $format;
+               $this->entityType = $entityType;
+               $this->entityId = $entityId;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * This implements lazy initialization of the entity: when called for 
the first time,
+        * this method will call getEntity() on the EntityHolder passed to the 
constructor,
+        * and then calls copy() on the entity returned. The resulting copy is 
returned.
+        * Subsequent calls will return the same entity.
+        *
+        * @param string $expectedClass The class with which the result is 
expected to be compatible.
+        * Defaults to Entity.
+        *
+        * @throws RuntimeException If the entity held by this EntityHolder is 
not compatible with $expectedClass.
+        * @return Entity
+        */
+       public function getEntity( $expectedClass = 
'Wikibase\DataModel\Entity\Entity' ) {
+               if ( !$this->entity ) {
+                       $this->entity = $this->codec->decodeEntity( 
$this->blob, $this->format );
+
+                       if ( !( $this->entity instanceof $expectedClass ) ) {
+                               throw new RuntimeException( 'Deferred decoding 
resulted in an incompatible entity. ' .
+                                       'Expected ' . $expectedClass . ', got ' 
. get_class(  ) );
+                       }
+               }
+
+               return $this->entity;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * @return EntityId|null
+        */
+       public function getEntityId() {
+               if ( !$this->entityId ) {
+                       $this->entityId = $this->getEntity()->getId();
+               }
+
+               return $this->entityId;
+       }
+
+       /**
+        * @see EntityHolder::getEntityType
+        *
+        * @return string
+        */
+       public function getEntityType() {
+               return $this->entityType;
+       }
+
+}
diff --git a/extensions/Wikibase/repo/includes/content/EntityContent.php 
b/extensions/Wikibase/repo/includes/content/EntityContent.php
index 4468291..af84f27 100644
--- a/extensions/Wikibase/repo/includes/content/EntityContent.php
+++ b/extensions/Wikibase/repo/includes/content/EntityContent.php
@@ -20,6 +20,9 @@
 use Title;
 use User;
 use ValueValidators\Result;
+use Wikibase\Content\DeferredCopyEntityHolder;
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\StatementListProvider;
@@ -90,7 +93,7 @@
                        return $this->getContentHandler()->supportsRedirects();
                }
 
-               return $this->getEntity()->getId() !== null;
+               return $this->getEntityId() !== null;
        }
 
        /**
@@ -122,6 +125,14 @@
        abstract public function getEntity();
 
        /**
+        * Returns a holder for the entity contained in this EntityContent 
object.
+        *
+        * @throws MWException when it's a redirect (targets will never be 
resolved)
+        * @return EntityHolder
+        */
+       abstract protected function getEntityHolder();
+
+       /**
         * Returns the ID of the entity represented by this EntityContent;
         *
         * @throws RuntimeException if no entity ID is set
@@ -131,13 +142,14 @@
                if ( $this->isRedirect() ) {
                        return $this->getEntityRedirect()->getEntityId();
                } else {
-                       if ( !$this->getEntity()->getId() ) {
+                       $id = $this->getEntityHolder()->getEntityId();
+                       if ( !$id ) {
                                // @todo: Force an ID to be present; Entity 
objects without an ID make sense,
                                // EntityContent objects with no entity ID 
don't.
                                throw new RuntimeException( 'EntityContent was 
constructed without an EntityId!' );
                        }
 
-                       return $this->getEntity()->getId();
+                       return $id;
                }
        }
 
@@ -493,17 +505,17 @@
                        return false;
                }
 
-               $thisEntity = $this->getEntity();
-               $thatEntity = $that->getEntity();
-
-               $thisId = $thisEntity->getId();
-               $thatId = $thatEntity->getId();
+               $thisId = $this->getEntityHolder()->getEntityId();
+               $thatId = $that->getEntityHolder()->getEntityId();
 
                if ( $thisId !== null && $thatId !== null
                        && !$thisId->equals( $thatId )
                ) {
                        return false;
                }
+
+               $thisEntity = $this->getEntity();
+               $thatEntity = $that->getEntity();
 
                return $thisEntity->equals( $thatEntity );
        }
@@ -576,7 +588,7 @@
                                        . $this->getModel() . '!' );
                        }
                } else {
-                       $patched = $handler->makeEntityContent( 
$entityAfterPatch );
+                       $patched = $handler->makeEntityContent( new 
EntityInstanceHolder( $entityAfterPatch ) );
                }
 
                return $patched;
@@ -648,7 +660,7 @@
                if ( $this->isRedirect() ) {
                        return $handler->makeEntityRedirectContent( 
$this->getEntityRedirect() );
                } else {
-                       return $handler->makeEntityContent( 
$this->getEntity()->copy() );
+                       return $handler->makeEntityContent( new 
DeferredCopyEntityHolder( $this->getEntityHolder() ) );
                }
        }
 
diff --git a/extensions/Wikibase/repo/includes/content/EntityContentFactory.php 
b/extensions/Wikibase/repo/includes/content/EntityContentFactory.php
index a58a557..ee78308 100644
--- a/extensions/Wikibase/repo/includes/content/EntityContentFactory.php
+++ b/extensions/Wikibase/repo/includes/content/EntityContentFactory.php
@@ -9,6 +9,7 @@
 use Status;
 use Title;
 use User;
+use Wikibase\Content\EntityInstanceHolder;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\EntityIdParsingException;
@@ -196,7 +197,7 @@
         */
        public function newFromEntity( Entity $entity ) {
                $handler = $this->getContentHandlerForType( $entity->getType() 
);
-               return $handler->makeEntityContent( $entity );
+               return $handler->makeEntityContent( new EntityInstanceHolder( 
$entity ) );
        }
 
        /**
diff --git a/extensions/Wikibase/repo/includes/content/EntityHandler.php 
b/extensions/Wikibase/repo/includes/content/EntityHandler.php
index 3bb2c61..7a7f942 100644
--- a/extensions/Wikibase/repo/includes/content/EntityHandler.php
+++ b/extensions/Wikibase/repo/includes/content/EntityHandler.php
@@ -16,6 +16,8 @@
 use Revision;
 use Title;
 use User;
+use Wikibase\Content\DeferredDecodingEntityHolder;
+use Wikibase\Content\EntityHolder;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\EntityIdParser;
@@ -307,18 +309,17 @@
        /**
         * Creates a Content object for the given Entity object.
         *
-        * @since 0.4
+        * @since 0.5
         *
-        * @param Entity $entity
+        * @param EntityHolder $entityHolder
         *
-        * @throws InvalidArgumentException
         * @return EntityContent
         */
-       public function makeEntityContent( Entity $entity ) {
+       public function makeEntityContent( EntityHolder $entityHolder ) {
                $contentClass = $this->getContentClass();
 
                /* EntityContent $content */
-               $content = new $contentClass( $entity );
+               $content = new $contentClass( $entityHolder );
 
                //TODO: make sure the entity is valid/complete!
 
@@ -362,31 +363,25 @@
                        $redirect = $content->getEntityRedirect();
                        return $this->contentCodec->encodeRedirect( $redirect, 
$format );
                } else {
+                       //TODO: if we have an un-decoded Entity in a 
DeferredDecodingEntityHolder, just re-use the encoded form.
                        $entity = $content->getEntity();
                        return $this->contentCodec->encodeEntity( $entity, 
$format );
                }
        }
 
-
        /**
         * @see ContentHandler::unserializeContent
         *
         * @param string $blob
-        * @param null|string $format
+        * @param string $format
         *
         * @throws MWContentSerializationException
         * @return EntityContent
         */
-       public function unserializeContent( $blob, $format = null ) {
-               $entity = $this->contentCodec->decodeEntity( $blob, $format );
+       public function unserializeContent( $blob, $format = 
CONTENT_FORMAT_JSON ) {
+               $redirect = $this->contentCodec->decodeRedirect( $blob, $format 
);
 
-               if ( $entity ) {
-                       $entityContent = $this->makeEntityContent( $entity );
-                       return $entityContent;
-               } else {
-                       // Must be a redirect then
-                       $redirect = $this->contentCodec->decodeRedirect( $blob, 
$format );
-
+               if ( $redirect ) {
                        if ( $redirect === null ) {
                                throw new MWContentSerializationException(
                                        'The serialized data contains neither 
an Entity nor an EntityRedirect!'
@@ -394,6 +389,11 @@
                        }
 
                        return $this->makeEntityRedirectContent( $redirect );
+               } else {
+                       $holder = new DeferredDecodingEntityHolder( 
$this->contentCodec, $blob, $format, $this->getEntityType() );
+                       $entityContent = $this->makeEntityContent( $holder );
+
+                       return $entityContent;
                }
        }
 
diff --git a/extensions/Wikibase/repo/includes/content/EntityHolder.php 
b/extensions/Wikibase/repo/includes/content/EntityHolder.php
new file mode 100644
index 0000000..c595023
--- /dev/null
+++ b/extensions/Wikibase/repo/includes/content/EntityHolder.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Wikibase\Content;
+
+use RuntimeException;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\EntityId;
+
+/**
+ * A holder for entities.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+interface EntityHolder {
+
+       /**
+        * Returns the entity held by this EntityHolder.
+        * Depending on the implementation, this operation may be expensive or 
trivial.
+        *
+        * @param string $expectedClass The class with which the result is 
expected to be compatible.
+        * Defaults to Entity.
+        *
+        * @throws RuntimeException If the entity held by this EntityHolder is 
not compatible with $expectedClass.
+        * @return Entity
+        */
+       public function getEntity( $expectedClass = 
'Wikibase\DataModel\Entity\Entity' );
+
+       /**
+        * Returns the ID of the entity held by this EntityHolder.
+        * May or may not require the actual entity to be instantiated.
+        * May be null if the Entity does not have an ID set.
+        *
+        * @return EntityId|null
+        */
+       public function getEntityId();
+
+       /**
+        * Returns the type of the entity held by this EntityHolder.
+        * May or may not require the actual entity or the entity's ID to be 
instantiated.
+        * Implementations must make sure that this is never null.
+        *
+        * @return string
+        */
+       public function getEntityType();
+
+}
diff --git a/extensions/Wikibase/repo/includes/content/EntityInstanceHolder.php 
b/extensions/Wikibase/repo/includes/content/EntityInstanceHolder.php
new file mode 100644
index 0000000..7f8402d
--- /dev/null
+++ b/extensions/Wikibase/repo/includes/content/EntityInstanceHolder.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Wikibase\Content;
+
+use RuntimeException;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\EntityId;
+
+/**
+ * Trivial EntityHolder holding an Entity object.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class EntityInstanceHolder implements EntityHolder {
+
+       /**
+        * @var Entity
+        */
+       private $entity;
+
+       /**
+        * @param Entity $entity
+        */
+       public function __construct( Entity $entity ) {
+               $this->entity = $entity;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * @param string $expectedClass The class the result is expected to be 
compatible with.
+        * Defaults to Entity.
+        *
+        * @throws RuntimeException If the entity held by this EntityHolder is 
not compatible with $expectedClass.
+        * @return Entity
+        */
+       public function getEntity( $expectedClass = 
'Wikibase\DataModel\Entity\Entity' ) {
+               if ( !( $this->entity instanceof $expectedClass ) ) {
+                       throw new RuntimeException( 'Contained entity is not 
compatible with ' . $expectedClass );
+               }
+
+               return $this->entity;
+       }
+
+       /**
+        * @see EntityHolder::getEntityId
+        *
+        * @return EntityId|null
+        */
+       public function getEntityId() {
+               return $this->entity->getId();
+       }
+
+       /**
+        * @see EntityHolder::getEntityType
+        *
+        * @return string
+        */
+       public function getEntityType() {
+               return $this->entity->getType();
+       }
+
+}
diff --git a/extensions/Wikibase/repo/includes/content/ItemContent.php 
b/extensions/Wikibase/repo/includes/content/ItemContent.php
index 4f59b97..037a465 100644
--- a/extensions/Wikibase/repo/includes/content/ItemContent.php
+++ b/extensions/Wikibase/repo/includes/content/ItemContent.php
@@ -5,7 +5,11 @@
 use InvalidArgumentException;
 use LogicException;
 use MWException;
+use RuntimeException;
 use Title;
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
+use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\Lib\Store\EntityRedirect;
 use Wikibase\Repo\ItemSearchTextGenerator;
@@ -50,22 +54,26 @@
         * In other words: treat as protected (which it was, but now cannot
         * be since we derive from Content).
         *
-        * @param Item|null $item
+        * @param EntityHolder|null $itemHolder
         * @param EntityRedirect|null $entityRedirect
         * @param Title|null $redirectTitle
         *
         * @throws InvalidArgumentException
         */
        public function __construct(
-               Item $item = null,
+               EntityHolder $itemHolder = null,
                EntityRedirect $entityRedirect = null,
                Title $redirectTitle = null
        ) {
                parent::__construct( CONTENT_MODEL_WIKIBASE_ITEM );
 
-               if ( is_null( $item ) === is_null( $entityRedirect ) ) {
+               if ( is_null( $itemHolder ) === is_null( $entityRedirect ) ) {
                        throw new InvalidArgumentException(
                                'Either $item or $entityRedirect and 
$redirectTitle must be provided.' );
+               }
+
+               if ( $itemHolder !== null && $itemHolder->getEntityType() !== 
Item::ENTITY_TYPE ) {
+                       throw new InvalidArgumentException( '$itemHolder must 
contain a Item entity!' );
                }
 
                if ( is_null( $entityRedirect ) !== is_null( $redirectTitle ) ) 
{
@@ -83,7 +91,7 @@
                        }
                }
 
-               $this->item = $item;
+               $this->itemHolder = $itemHolder;
                $this->redirect = $entityRedirect;
                $this->redirectTitle = $redirectTitle;
        }
@@ -96,7 +104,7 @@
         * @return ItemContent
         */
        public static function newFromItem( Item $item ) {
-               return new static( $item );
+               return new static( new EntityInstanceHolder( $item ) );
        }
 
        /**
@@ -145,11 +153,16 @@
                        throw new MWException( 'Unresolved redirect to [[' . 
$redirect->getFullText() . ']]' );
                }
 
-               if ( !$this->item ) {
+               if ( !$this->itemHolder ) {
                        throw new LogicException( 'Neither redirect nor item 
found in ItemContent!' );
                }
 
-               return $this->item;
+               wfProfileIn( __METHOD__ );
+
+               $item = $this->itemHolder->getEntity( 
'Wikibase\DataModel\Entity\Item' );
+
+               wfProfileOut( __METHOD__ );
+               return $item;
        }
 
        /**
@@ -158,7 +171,7 @@
         * @return ItemContent
         */
        public static function newEmpty() {
-               return new static( Item::newEmpty() );
+               return new static( new EntityInstanceHolder( Item::newEmpty() ) 
);
        }
 
        /**
@@ -172,6 +185,15 @@
        }
 
        /**
+        * @see EntityContent::getEntityHolder
+        *
+        * @return EntityHolder
+        */
+       protected function getEntityHolder() {
+               return $this->itemHolder;
+       }
+
+       /**
         * @see EntityContent::getTextForSearchIndex()
         */
        public function getTextForSearchIndex() {
diff --git a/extensions/Wikibase/repo/includes/content/PropertyContent.php 
b/extensions/Wikibase/repo/includes/content/PropertyContent.php
index d36b79e..b8b40d0 100644
--- a/extensions/Wikibase/repo/includes/content/PropertyContent.php
+++ b/extensions/Wikibase/repo/includes/content/PropertyContent.php
@@ -2,6 +2,9 @@
 
 namespace Wikibase;
 
+use InvalidArgumentException;
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
 use Wikibase\DataModel\Entity\Property;
 
 /**
@@ -15,9 +18,9 @@
 class PropertyContent extends EntityContent {
 
        /**
-        * @var Property
+        * @var EntityHolder
         */
-       private $property;
+       private $propertyHolder;
 
        /**
         * Do not use to construct new stuff from outside of this class,
@@ -28,11 +31,17 @@
         *
         * @protected
         *
-        * @param Property $property
+        * @param EntityHolder $propertyHolder
+        * @throws InvalidArgumentException
         */
-       public function __construct( Property $property ) {
+       public function __construct( EntityHolder $propertyHolder ) {
                parent::__construct( CONTENT_MODEL_WIKIBASE_PROPERTY );
-               $this->property = $property;
+
+               if ( $propertyHolder->getEntityType() !== Property::ENTITY_TYPE 
) {
+                       throw new InvalidArgumentException( '$propertyHolder 
must contain a Property entity!' );
+               }
+
+               $this->propertyHolder = $propertyHolder;
        }
 
        /**
@@ -43,7 +52,7 @@
         * @return PropertyContent
         */
        public static function newFromProperty( Property $property ) {
-               return new static( $property );
+               return new static( new EntityInstanceHolder( $property ) );
        }
 
        /**
@@ -52,16 +61,7 @@
         * @return Property
         */
        public function getProperty() {
-               return $this->property;
-       }
-
-       /**
-        * Sets the property that makes up this property content.
-        *
-        * @param Property $property
-        */
-       public function setProperty( Property $property ) {
-               $this->property = $property;
+               return $this->propertyHolder->getEntity( 
'Wikibase\DataModel\Entity\Property' );
        }
 
        /**
@@ -70,7 +70,7 @@
         * @return PropertyContent
         */
        public static function newEmpty() {
-               return new static( Property::newFromType( 'string' ) );
+               return new static( new EntityInstanceHolder( 
Property::newFromType( 'string' ) ) );
        }
 
        /**
@@ -79,7 +79,16 @@
         * @return Property
         */
        public function getEntity() {
-               return $this->property;
+               return $this->getProperty();
+       }
+
+       /**
+        * @see EntityContent::getEntityHolder
+        *
+        * @return EntityHolder
+        */
+       public function getEntityHolder() {
+               return $this->propertyHolder;
        }
 
        /**
@@ -94,6 +103,7 @@
                        return false;
                }
 
+               //TODO: provide a way to get the data type from the holder 
directly!
                if ( is_null( $this->getEntity()->getDataTypeId() ) ) {
                        return false;
                }
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/ContentRetrieverTest.php 
b/extensions/Wikibase/repo/tests/phpunit/includes/ContentRetrieverTest.php
deleted file mode 100644
index f488a4c..0000000
--- a/extensions/Wikibase/repo/tests/phpunit/includes/ContentRetrieverTest.php
+++ /dev/null
@@ -1,203 +0,0 @@
-<?php
-
-namespace Wikibase\Test;
-
-use Revision;
-use Title;
-use Wikibase\ContentRetriever;
-use Wikibase\DataModel\Entity\Item;
-use Wikibase\ItemContent;
-use Wikibase\Repo\WikibaseRepo;
-
-/**
- * @covers Wikibase\ContentRetriever
- *
- * @licence GNU GPL v2+
- * @author Katie Filbert < [email protected] >
- *
- * @group Wikibase
- * @group WikibaseRepo
- *
- * The database group has as a side effect that temporal database tables are 
created. This makes
- * it possible to test without poisoning a production database.
- * @group Database
- *
- * Some of the tests takes more time, and needs therefor longer time before 
they can be aborted
- * as non-functional. The reason why tests are aborted is assumed to be set up 
of temporal databases
- * that hold the first tests in a pending state awaiting access to the 
database.
- * @group medium
- */
-class ContentRetrieverTest extends \MediaWikiTestCase {
-
-       /**
-        * @param int $oldId
-        * @param Revision $revision
-        * @param Title $title
-        *
-        * @return \PHPUnit_Framework_MockObject_MockObject
-        */
-       private function getArticleMock( $oldId, Revision $revision, Title 
$title )
-       {
-               $context = $this->getContextMock( $title );
-               $page = $this->getPageMock( $revision );
-
-               $article = $this->getMockBuilder( 'Article' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $article->expects( $this->any() )
-                       ->method( 'getContext' )
-                       ->will( $this->returnValue( $context ) );
-
-               $article->expects( $this->any() )
-                       ->method( 'getOldID' )
-                       ->will( $this->returnValue( $oldId ) );
-
-               $article->expects( $this->any() )
-                       ->method( 'getPage' )
-                       ->will( $this->returnValue( $page ) );
-
-               $article->expects( $this->any() )
-                       ->method( 'getRevisionFetched' )
-                       ->will( $this->returnValue( $revision ) );
-
-               $article->expects( $this->any() )
-                       ->method( 'getTitle' )
-                       ->will( $this->returnValue( $title ) );
-
-               return $article;
-       }
-
-       /**
-        * @param Title $title
-        *
-        * @return \PHPUnit_Framework_MockObject_MockObject
-        */
-       private function getContextMock( Title $title )
-       {
-               $context = $this->getMockBuilder( 'IContextSource' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $context->expects( $this->any() )
-                       ->method( 'getLanguage' )
-                       ->will( $this->returnValue( $title->getPageLanguage() ) 
);
-
-               $context->expects( $this->any() )
-                       ->method( 'getTitle' )
-                       ->will( $this->returnValue( $title ) );
-
-               return $context;
-       }
-
-       /**
-        * @param Revision $revision
-        *
-        * @return \PHPUnit_Framework_MockObject_MockObject
-        */
-       private function getPageMock( Revision $revision )
-       {
-               $page = $this->getMockBuilder( 'WikiPage' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $page->expects( $this->any() )
-                       ->method( 'getContentHandler' )
-                       ->will( $this->returnValue( 
$revision->getContentHandler() ) );
-
-               return $page;
-       }
-
-       /**
-        * @param array $queryParams
-        *
-        * @return \PHPUnit_Framework_MockObject_MockObject
-        */
-       private function getRequestMock( array $queryParams )
-       {
-               $request = $this->getMockBuilder( 'WebRequest' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $request->expects( $this->any() )
-                       ->method( 'getCheck' )
-                       ->will( $this->returnValue( isset( $queryParams['diff'] 
) ) );
-
-               $request->expects( $this->any() )
-                       ->method( 'getQueryValues' )
-                       ->will( $this->returnValue( $queryParams ) );
-
-               $request->expects( $this->any() )
-                       ->method( 'getVal' )
-                       ->will( $this->returnValue( isset( $queryParams['diff'] 
) ? $queryParams['diff'] : null ) );
-
-               return $request;
-       }
-
-       public function testGetContentForRequest() {
-               $cases = $this->getContentCases();
-
-               foreach( $cases as $case ) {
-                       /** @var Title $title */
-                       list( $expected, $oldId, $queryParams, $title, $message 
) = $case;
-
-                       $revision = Revision::newFromId( $oldId );
-
-                       $request = $this->getRequestMock( $queryParams );
-                       $article = $this->getArticleMock( $oldId, $revision, 
$title );
-
-                       $contentRetriever = new ContentRetriever();
-                       $content = $contentRetriever->getContentForRequest( 
$request, $article );
-
-                       $this->assertEquals( $expected, $content, $message );
-               }
-       }
-
-       private function getContentCases() {
-               $item = Item::newEmpty();
-
-               $descriptions = array(
-                       'Largest city in Germany',
-                       'Capital of Germany',
-                       'Best city in Germany'
-               );
-
-               //NOTE: we are assuming here that the item will get stored as a 
wiki page!
-               $store = WikibaseRepo::getDefaultInstance()->getEntityStore();
-               $titleLookup = 
WikibaseRepo::getDefaultInstance()->getEntityTitleLookup();
-
-               $rev = $store->saveEntity( $item, "test", $GLOBALS['wgUser'], 
EDIT_NEW );
-               $title = $titleLookup->getTitleForId( 
$rev->getEntity()->getId() );
-
-               $revIds = array();
-
-               foreach( $descriptions as $description ) {
-                       $item->setDescription( 'en', $description );
-
-                       $rev = $store->saveEntity( $item, "edit description", 
$GLOBALS['wgUser'], EDIT_UPDATE );
-                       $revIds[] = $rev->getRevisionId();
-               }
-
-               /**
-                * @var ItemContent $content2
-                * @var ItemContent $content3
-                */
-               $content2 = Revision::newFromId( $revIds[1] )->getContent();
-               $content3 = Revision::newFromId( $revIds[2] )->getContent();
-               $this->assertNotEquals( $content2, $content3, 'Setup failed' );
-
-               return array(
-                       array( $content3, $revIds[0], array( 'diff' => '0', 
'oldid' => $revIds[0] ), $title, 'diff=0' ),
-                       array( $content2, $revIds[0], array( 'diff' => 
$revIds[1], 'oldid' => $revIds[0] ), $title, 'rev id' ),
-                       array( $content2, $revIds[0], array( 'diff' => 'next', 
'oldid' => $revIds[0] ), $title, 'diff=next' ),
-                       array( $content2, $revIds[1], array( 'diff' => 'prev', 
'oldid' => $revIds[1] ), $title, 'diff=prev' ),
-                       array( $content3, $revIds[1], array( 'diff' => 'cur', 
'oldid' => $revIds[1] ), $title, 'diff=cur' ),
-                       array( $content3, $revIds[1], array( 'diff' => 'c', 
'oldid' => $revIds[1] ), $title, 'diff=c' ),
-                       array( $content3, $revIds[1], array( 'diff' => '0', 
'oldid' => $revIds[1] ), $title, 'diff=0' ),
-                       array( $content3, $revIds[1], array( 'diff' => '', 
'oldid' => $revIds[1] ), $title, 'diff=' ),
-                       array( $content3, $revIds[2], array(), $title, 'no 
query params' ),
-                       array( null, $revIds[1], array( 'diff' => '-1', 'oldid' 
=> $revIds[0] ), $title, 'diff=-1' )
-               );
-       }
-
-}
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorTest.php
index cc43627..8bfead7 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorTest.php
@@ -7,6 +7,7 @@
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\InMemoryDataTypeLookup;
 use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
 use Wikibase\DataModel\Snak\PropertyValueSnak;
 use Wikibase\EntityParserOutputGenerator;
@@ -39,12 +40,60 @@
 
                $parserOutput = $entityParserOutputGenerator->getParserOutput( 
$revision );
 
-               $this->assertEquals( self::$html, $parserOutput->getText() );
-               $this->assertEquals( self::$placeholders, 
$parserOutput->getExtensionData( 'wikibase-view-chunks' ) );
-               $this->assertEquals( self::$configVars, 
$parserOutput->getJsConfigVars() );
+               $this->assertEquals(
+                       self::$html,
+                       $parserOutput->getText(),
+                       'html text'
+               );
 
-               $this->assertEquals( array( 'http://an.url.com', 
'https://another.url.org' ), array_keys( $parserOutput->getExternalLinks() ) );
-               $this->assertEquals( array( 'File:This_is_a_file.pdf', 
'File:Selfie.jpg' ), array_keys( $parserOutput->getImages() ) );
+               $this->assertEquals(
+                       self::$placeholders,
+                       $parserOutput->getExtensionData( 'wikibase-view-chunks' 
),
+                       'view chunks'
+               );
+
+               $this->assertEquals(
+                       self::$configVars,
+                       $parserOutput->getJsConfigVars(),
+                       'config vars'
+               );
+
+               $this->assertEquals(
+                       'kitten item',
+                       $parserOutput->getExtensionData( 'wikibase-titletext' ),
+                       'title text'
+               );
+
+               $this->assertEquals(
+                       array( 'http://an.url.com', 'https://another.url.org' ),
+                       array_keys( $parserOutput->getExternalLinks() ),
+                       'external links'
+               );
+
+               $this->assertEquals(
+                       array( 'File:This_is_a_file.pdf', 'File:Selfie.jpg' ),
+                       array_keys( $parserOutput->getImages() ),
+                       'images'
+               );
+       }
+
+       public function testTitleText_ItemHasNolabel() {
+               $entityParserOutputGenerator = 
$this->newEntityParserOutputGenerator();
+
+               $item = Item::newEmpty();
+               $item->setId( new ItemId( 'Q7799929' ) );
+               $item->setDescription( 'en', 'a kitten' );
+
+               $timestamp = wfTimestamp( TS_MW );
+               $revision = new EntityRevision( $item, 13045, $timestamp );
+
+               $parserOutput = $entityParserOutputGenerator->getParserOutput( 
$revision );
+
+               $this->assertEquals(
+                       'Q7799929',
+                       $parserOutput->getExtensionData( 'wikibase-titletext' ),
+                       'title text'
+               );
        }
 
        private function newEntityParserOutputGenerator() {
@@ -54,13 +103,39 @@
                        $this->getEntityTitleLookupMock(),
                        $this->getValuesFinder(),
                        new SqlEntityInfoBuilderFactory(),
-                       new LanguageFallbackChain( array() ),
+                       $this->newLanguageFallbackChain(),
                        'en'
                );
        }
 
+       private function newLanguageFallbackChain() {
+               $fallbackChain = $this->getMockBuilder( 
'Wikibase\LanguageFallbackChain' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $fallbackChain->expects( $this->any() )
+                       ->method( 'extractPreferredValue' )
+                       ->will( $this->returnCallback( function( $labels ) {
+                                       if ( array_key_exists( 'en', $labels ) 
) {
+                                               return array(
+                                                       'value' => 
$labels['en'],
+                                                       'language' => 'en',
+                                                       'source' => 'en'
+                                               );
+                                       }
+
+                                       return null;
+                               } ) );
+
+               return $fallbackChain;
+       }
+
        private function newItem() {
                $item = Item::newEmpty();
+               $item->setId( new ItemId( 'Q7799929' ) );
+
+               $item->setLabel( 'en', 'kitten item' );
+
                $statements = $item->getStatements();
 
                $statements->addNewStatement( new PropertyValueSnak( 42, new 
StringValue( 'http://an.url.com' ) ) );
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/actions/ViewEntityActionTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/actions/ViewEntityActionTest.php
index ac5174c..6974c24 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/actions/ViewEntityActionTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/actions/ViewEntityActionTest.php
@@ -99,6 +99,14 @@
                $this->assertNotRegExp( '/wikibase-edittoolbar-container/', 
$html, 'no edit toolbar' );
        }
 
+       public function testShowNonExistingRevision() {
+               $page = $this->getTestItemPage( 'Berlin' );
+               $params = array( 'oldid' => 95829689425 );
+
+               $html = $this->executeViewAction( $page, $params );
+               $this->assertContains( 'Die Version 95829689425', $html, 
'non-existing revision' );
+       }
+
        /**
         * @param WikiPage $page
         * @param string[] $params
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredCopyEntityHolderTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredCopyEntityHolderTest.php
new file mode 100644
index 0000000..416fb21
--- /dev/null
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredCopyEntityHolderTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Wikibase\Test;
+
+use Wikibase\Content\DeferredCopyEntityHolder;
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+
+/**
+ * @covers Wikibase\Content\DeferredCopyEntityHolder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseEntity
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class DeferredCopyEntityHolderTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @return Entity
+        */
+       private function newEntity() {
+               $item = Item::newEmpty();
+               $item->setId( new ItemId( 'Q17' ) );
+               $item->setLabel( 'en', 'Foo' );
+
+               return $item;
+       }
+
+       /**
+        * @param Entity $entity
+        *
+        * @return EntityHolder
+        */
+       private function newHolder( Entity $entity ) {
+               $holder = new EntityInstanceHolder( $entity );
+               return new DeferredCopyEntityHolder( $holder );
+       }
+
+       public function testGetEntity() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntity();
+
+               $this->assertNotSame( $entity, $actual );
+               $this->assertEquals( $entity->getId(), $actual->getId() );
+               $this->assertTrue( $entity->equals( $actual ) );
+       }
+
+       public function testGetEntityType() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityType();
+               $this->assertEquals( $entity->getType(), $actual );
+       }
+
+       public function testGetEntityId() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityId();
+               $this->assertEquals( $entity->getId(), $actual );
+       }
+
+}
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredDecodingEntityHolderTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredDecodingEntityHolderTest.php
new file mode 100644
index 0000000..3fdb3e5
--- /dev/null
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredDecodingEntityHolderTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Wikibase\Test;
+
+use Wikibase\Content\DeferredCopyEntityHolder;
+use Wikibase\Content\DeferredDecodingEntityHolder;
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Lib\Store\EntityContentDataCodec;
+use Wikibase\Repo\WikibaseRepo;
+
+/**
+ * @covers Wikibase\Content\DeferredDecodingEntityHolder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseEntity
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class DeferredDecodingEntityHolderTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @return Entity
+        */
+       private function newEntity() {
+               $item = Item::newEmpty();
+               $item->setId( new ItemId( 'Q17' ) );
+               $item->setLabel( 'en', 'Foo' );
+
+               return $item;
+       }
+
+       /**
+        * @param Entity $entity
+        *
+        * @return EntityHolder
+        */
+       private function newHolder( Entity $entity ) {
+               $codec = new EntityContentDataCodec(
+                       new BasicEntityIdParser(),
+                       
WikibaseRepo::getDefaultInstance()->getInternalEntitySerializer(),
+                       
WikibaseRepo::getDefaultInstance()->getInternalEntityDeserializer()
+               );
+
+               $blob = $codec->encodeEntity( $entity, CONTENT_FORMAT_JSON );
+
+               return new DeferredDecodingEntityHolder(
+                       $codec,
+                       $blob,
+                       CONTENT_FORMAT_JSON,
+                       $entity->getType(),
+                       $entity->getId() );
+       }
+
+       public function testGetEntity() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntity();
+               $this->assertNotSame( $entity, $actual );
+               $this->assertEquals( $entity->getId(), $actual->getId() );
+               $this->assertTrue( $entity->equals( $actual ) );
+       }
+
+       public function testGetEntityType() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityType();
+               $this->assertEquals( $entity->getType(), $actual );
+       }
+
+       public function testGetEntityId() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityId();
+               $this->assertEquals( $entity->getId(), $actual );
+       }
+
+}
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityHandlerTest.php 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityHandlerTest.php
index 7b1b54f..11c94f1 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityHandlerTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityHandlerTest.php
@@ -8,6 +8,7 @@
 use Revision;
 use RuntimeException;
 use Title;
+use Wikibase\Content\EntityInstanceHolder;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\EntityContent;
@@ -79,7 +80,7 @@
                }
 
                $handler = $this->getHandler();
-               return $handler->makeEntityContent( $entity );
+               return $handler->makeEntityContent( new EntityInstanceHolder( 
$entity ) );
        }
 
        /**
@@ -305,7 +306,7 @@
                $entity = $this->newEntity();
 
                $handler = $this->getHandler();
-               $content = $handler->makeEntityContent( $entity );
+               $content = $handler->makeEntityContent( new 
EntityInstanceHolder( $entity ) );
 
                $this->assertEquals( $this->getModelId(), $content->getModel() 
);
                $this->assertSame( $entity, $content->getEntity() );
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityInstanceHolderTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityInstanceHolderTest.php
new file mode 100644
index 0000000..a845db3
--- /dev/null
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityInstanceHolderTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Wikibase\Test;
+
+use Wikibase\Content\EntityHolder;
+use Wikibase\Content\EntityInstanceHolder;
+use Wikibase\DataModel\Entity\Entity;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+
+/**
+ * @covers Wikibase\Content\EntityInstanceHolder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseEntity
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class EntityInstanceHolderTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @return Entity
+        */
+       private function newEntity() {
+               $item = Item::newEmpty();
+               $item->setId( new ItemId( 'Q17' ) );
+               $item->setLabel( 'en', 'Foo' );
+
+               return $item;
+       }
+
+       /**
+        * @param Entity $entity
+        *
+        * @return EntityHolder
+        */
+       private function newHolder( Entity $entity ) {
+               return new EntityInstanceHolder( $entity );
+       }
+
+       public function testGetEntity() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntity();
+               $this->assertSame( $entity, $actual );
+       }
+
+       public function testGetEntityType() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityType();
+               $this->assertEquals( $entity->getType(), $actual );
+       }
+
+       public function testGetEntityId() {
+               $entity = $this->newEntity();
+               $holder = $this->newHolder( $entity );
+
+               $actual = $holder->getEntityId();
+               $this->assertEquals( $entity->getId(), $actual );
+       }
+
+}
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/ItemHandlerTest.php 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/ItemHandlerTest.php
index cc4830d..b9458c4 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/content/ItemHandlerTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/content/ItemHandlerTest.php
@@ -2,6 +2,7 @@
 
 namespace Wikibase\Test;
 
+use Wikibase\Content\EntityInstanceHolder;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\Item;
@@ -101,7 +102,7 @@
                        $entity->setId( new ItemId( 'Q42' ) );
                }
 
-               return $this->getHandler()->makeEntityContent( $entity );
+               return $this->getHandler()->makeEntityContent( new 
EntityInstanceHolder( $entity ) );
        }
 
        public function testMakeEntityRedirectContent() {
diff --git a/vendor/composer/autoload_classmap.php 
b/vendor/composer/autoload_classmap.php
index a7417cc..11aa86c 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -427,7 +427,10 @@
     'Wikibase\\Client\\Usage\\UsageTracker' => $baseDir . 
'/extensions/Wikibase/client/includes/Usage/UsageTracker.php',
     'Wikibase\\Client\\Usage\\UsageTrackerException' => $baseDir . 
'/extensions/Wikibase/client/includes/Usage/UsageTrackerException.php',
     'Wikibase\\Client\\WikibaseClient' => $baseDir . 
'/extensions/Wikibase/client/includes/WikibaseClient.php',
-    'Wikibase\\ContentRetriever' => $baseDir . 
'/extensions/Wikibase/repo/includes/ContentRetriever.php',
+    'Wikibase\\Content\\DeferredCopyEntityHolder' => $baseDir . 
'/extensions/Wikibase/repo/includes/content/DeferredCopyEntityHolder.php',
+    'Wikibase\\Content\\DeferredDecodingEntityHolder' => $baseDir . 
'/extensions/Wikibase/repo/includes/content/DeferredDecodingEntityHolder.php',
+    'Wikibase\\Content\\EntityHolder' => $baseDir . 
'/extensions/Wikibase/repo/includes/content/EntityHolder.php',
+    'Wikibase\\Content\\EntityInstanceHolder' => $baseDir . 
'/extensions/Wikibase/repo/includes/content/EntityInstanceHolder.php',
     'Wikibase\\CopyrightMessageBuilder' => $baseDir . 
'/extensions/Wikibase/repo/includes/CopyrightMessageBuilder.php',
     'Wikibase\\CreateBlacklistedItems' => $baseDir . 
'/extensions/Wikibase/repo/maintenance/createBlacklistedItems.php',
     'Wikibase\\DataAccess\\PropertyIdResolver' => $baseDir . 
'/extensions/Wikibase/client/includes/DataAccess/PropertyIdResolver.php',
@@ -958,8 +961,9 @@
     'Wikibase\\Test\\ClaimsSerializerTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/serializers/ClaimsSerializerTest.php',
     'Wikibase\\Test\\ClaimsViewTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/View/ClaimsViewTest.php',
     'Wikibase\\Test\\ClientDefaultsTest' => $baseDir . 
'/extensions/Wikibase/client/tests/phpunit/ClientDefaultsTest.php',
-    'Wikibase\\Test\\ContentRetrieverTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/ContentRetrieverTest.php',
     'Wikibase\\Test\\CopyrightMessageBuilderTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/CopyrightMessageBuilderTest.php',
+    'Wikibase\\Test\\DeferredCopyEntityHolderTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredCopyEntityHolderTest.php',
+    'Wikibase\\Test\\DeferredDecodingEntityHolderTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/content/DeferredDecodingEntityHolderTest.php',
     'Wikibase\\Test\\DeletePageNoticeCreatorTest' => $baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/hooks/DeletePageNoticeCreatorTest.php',
     'Wikibase\\Test\\DescriptionSerializerTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/serializers/DescriptionSerializerTest.php',
     'Wikibase\\Test\\DiffChangeTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/changes/DiffChangeTest.php',
@@ -989,6 +993,7 @@
     'Wikibase\\Test\\EntityIdTitleFormatterTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/formatters/EntityIdTitleFormatterTest.php',
     'Wikibase\\Test\\EntityInfoBuilderTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/store/EntityInfoBuilderTest.php',
     'Wikibase\\Test\\EntityInfoTermLookupTest' => $baseDir . 
'/extensions/Wikibase/lib/tests/phpunit/store/EntityInfoTermLookupTest.php',
+    'Wikibase\\Test\\EntityInstanceHolderTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/content/EntityInstanceHolderTest.php',
     'Wikibase\\Test\\EntityModificationTestHelper' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/EntityModificationTestHelper.php',
     'Wikibase\\Test\\EntityNamespaceLookupTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/EntityNamespaceLookupTest.php',
     'Wikibase\\Test\\EntityParserOutputGeneratorFactoryTest' => $baseDir . 
'/extensions/Wikibase/repo/tests/phpunit/includes/EntityParserOutputGeneratorFactoryTest.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 5ccf1b5..8c586cc 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -1325,7 +1325,7 @@
         "source": {
             "type": "git",
             "url": 
"https://git.wikimedia.org/git/mediawiki/extensions/Wikibase.git";,
-            "reference": "3a65287aaeb3bf6109c8e7ad557100b04f19c47a"
+            "reference": "563b0cd967edf27fa2d9cd972b03bf4f902c8e06"
         },
         "require": {
             "data-values/common": "~0.2.0",
@@ -1352,7 +1352,7 @@
         "conflict": {
             "mediawiki/mediawiki": "<1.23"
         },
-        "time": "2014-12-10 10:40:44",
+        "time": "2014-12-15 12:01:25",
         "type": "mediawiki-extension",
         "installation-source": "source",
         "autoload": {

-- 
To view, visit https://gerrit.wikimedia.org/r/179904
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I41d67d9561ea197bcf8d14f63c566972c83c4ab4
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikidata
Gerrit-Branch: wmf/1.25wmf12
Gerrit-Owner: Aude <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to