diff --git a/app/code/Magento/Backend/Block/Template.php b/app/code/Magento/Backend/Block/Template.php
index 1bcd3033035dc0bf2de89782ad8972b8236e0d96..45d3830793e116f8d7029b4ae5c87ca80a83f280 100644
--- a/app/code/Magento/Backend/Block/Template.php
+++ b/app/code/Magento/Backend/Block/Template.php
@@ -70,17 +70,12 @@ class Template extends \Magento\Framework\View\Element\Template
      *
      * @param string $moduleName Full module name
      * @return boolean
+     * @deprecated
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function isOutputEnabled($moduleName = null)
     {
-        if ($moduleName === null) {
-            $moduleName = $this->getModuleName();
-        }
-
-        return !$this->_scopeConfig->isSetFlag(
-            'advanced/modules_disable_output/' . $moduleName,
-            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-        );
+        return true;
     }
 
     /**
diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml
index 813a097ea6bf3ca2e615f0848619409c61f2dd9f..fc6c8df551913f5ad34d9875bc7e0e9173424681 100644
--- a/app/code/Magento/Backend/etc/adminhtml/system.xml
+++ b/app/code/Magento/Backend/etc/adminhtml/system.xml
@@ -16,7 +16,7 @@
         <tab id="advanced" translate="label" sortOrder="999999">
             <label>Advanced</label>
         </tab>
-        <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="1" showInWebsite="1" showInStore="1">
+        <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="0" showInWebsite="0" showInStore="0">
             <label>Advanced</label>
             <tab>advanced</tab>
             <resource>Magento_Backend::advanced</resource>
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php
index 0c6f680df486a50c584008b9cee288366c5d0209..1ca01ca6724ce741b58278ffe2d42a8522aa574c 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php
@@ -55,12 +55,6 @@ class AjaxTest extends \PHPUnit_Framework_TestCase
             ->getMock();
         $eventManager->expects($this->exactly(2))->method('dispatch')->will($this->returnValue(true));
 
-        $scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config::class)
-            ->setMethods(['getValue'])
-            ->disableOriginalConstructor()->getMock();
-        $scopeConfig->expects($this->once())->method('getValue')->withAnyParameters()
-            ->will($this->returnValue(false));
-
         $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor()
             ->setMethods(['setStoreId', 'load', 'getId', '__wakeup', '__sleep'])
             ->getMock();
@@ -93,8 +87,6 @@ class AjaxTest extends \PHPUnit_Framework_TestCase
 
         $this->context->expects($this->once())->method('getEventManager')
             ->will($this->returnValue($eventManager));
-        $this->context->expects($this->once())->method('getScopeConfig')
-            ->will($this->returnValue($scopeConfig));
         $this->context->expects($this->once())->method('getLayout')
             ->will($this->returnValue($layout));
         $this->context->expects($this->once())->method('getRequest')
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
index 9c153fb920279c48ac2e36014a178b67de66eb3f..90d76e469b7abe4d0c2e253bd174f0ee8437dc00 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php
@@ -63,7 +63,7 @@ class NewWidgetTest extends \PHPUnit_Framework_TestCase
             false,
             false
         );
-        $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, ['getValue'], [], '', false, false);
+        $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, [], [], '', false, false);
         $this->cacheState = $this->getMock(
             \Magento\Framework\App\Cache\State::class,
             ['isEnabled'],
@@ -188,8 +188,6 @@ class NewWidgetTest extends \PHPUnit_Framework_TestCase
     {
         $this->eventManager->expects($this->exactly(2))->method('dispatch')
             ->will($this->returnValue(true));
-        $this->scopeConfig->expects($this->once())->method('getValue')->withAnyParameters()
-            ->willReturn(false);
         $this->cacheState->expects($this->atLeastOnce())->method('isEnabled')->withAnyParameters()
             ->willReturn(false);
         $this->catalogConfig->expects($this->once())->method('getProductAttributes')
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
index 349a4772633ed086905183a7cc826b26782967d6..de647a34619dfe0355b9ff79277e3fc3ec24d131 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
@@ -7,6 +7,9 @@ namespace Magento\CatalogUrlRewrite\Model\Category;
 
 use Magento\Catalog\Model\Category;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+use Magento\Framework\App\ObjectManager;
 
 class ChildrenUrlRewriteGenerator
 {
@@ -16,16 +19,25 @@ class ChildrenUrlRewriteGenerator
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory */
     protected $categoryUrlRewriteGeneratorFactory;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider
      * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         ChildrenCategoriesProvider $childrenCategoriesProvider,
-        CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory
+        CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->childrenCategoriesProvider = $childrenCategoriesProvider;
         $this->categoryUrlRewriteGeneratorFactory = $categoryUrlRewriteGeneratorFactory;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -33,19 +45,22 @@ class ChildrenUrlRewriteGenerator
      *
      * @param int $storeId
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($storeId, Category $category)
+    public function generate($storeId, Category $category, $rootCategoryId = null)
     {
-        $urls = [];
-        foreach ($this->childrenCategoriesProvider->getChildren($category) as $childCategory) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
             $childCategory->setStoreId($storeId);
             $childCategory->setData('save_rewrites_history', $category->getData('save_rewrites_history'));
-            $urls = array_merge(
-                $urls,
-                $this->categoryUrlRewriteGeneratorFactory->create()->generate($childCategory)
+            /** @var CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator */
+            $categoryUrlRewriteGenerator = $this->categoryUrlRewriteGeneratorFactory->create();
+            $mergeDataProvider->merge(
+                $categoryUrlRewriteGenerator->generate($childCategory, false, $rootCategoryId)
             );
         }
-        return $urls;
+
+        return $mergeDataProvider->getData();
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
index d4581744bcb07ec86b60e34e5c80ce0e46878d85..c994b424641470bf728765594460266f3c5311f2 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
@@ -5,13 +5,8 @@
  */
 namespace Magento\CatalogUrlRewrite\Model\Category;
 
-use Magento\Catalog\Model\Category;
-use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
 use Magento\UrlRewrite\Model\OptionProvider;
-use Magento\UrlRewrite\Model\UrlFinderInterface;
-use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
-use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
 
 class CurrentUrlRewritesRegenerator
 {
@@ -21,25 +16,53 @@ class CurrentUrlRewritesRegenerator
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory */
     protected $urlRewriteFactory;
 
-    /** @var UrlFinderInterface */
-    protected $urlFinder;
+    /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite */
+    private $urlRewritePrototype;
 
-    /** @var \Magento\Catalog\Model\Category */
+    /**
+     * @var \Magento\Catalog\Model\Category
+     * @deprecated
+     */
     protected $category;
 
+    /**
+     * @var \Magento\UrlRewrite\Model\UrlFinderInterface
+     * @deprecated
+     */
+    protected $urlFinder;
+
+    /** @var \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder */
+    private $urlRewriteFinder;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory
-     * @param UrlFinderInterface $urlFinder
+     * @param \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder
+     * @param \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder|null $urlRewriteFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
-        CategoryUrlPathGenerator $categoryUrlPathGenerator,
-        UrlRewriteFactory $urlRewriteFactory,
-        UrlFinderInterface $urlFinder
+        \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator,
+        \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory,
+        \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder,
+        \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder $urlRewriteFinder = null,
+        \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
         $this->urlRewriteFactory = $urlRewriteFactory;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
         $this->urlFinder = $urlFinder;
+        $this->urlRewriteFinder = $urlRewriteFinder ?: \Magento\Framework\App\ObjectManager::getInstance()
+            ->get(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class);
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory =  \Magento\Framework\App\ObjectManager::getInstance()->get(
+                \Magento\UrlRewrite\Model\MergeDataProviderFactory::class
+            );
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -47,70 +70,70 @@ class CurrentUrlRewritesRegenerator
      *
      * @param int $storeId
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($storeId, Category $category)
+    public function generate($storeId, \Magento\Catalog\Model\Category $category, $rootCategoryId = null)
     {
-        $this->category = $category;
-
-        $currentUrlRewrites = $this->urlFinder->findAllByData(
-            [
-                UrlRewrite::STORE_ID => $storeId,
-                UrlRewrite::ENTITY_ID => $category->getId(),
-                UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
-            ]
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $currentUrlRewrites = $this->urlRewriteFinder->findAllByData(
+            $category->getEntityId(),
+            $storeId,
+            CategoryUrlRewriteGenerator::ENTITY_TYPE,
+            $rootCategoryId
         );
 
-        $urlRewrites = [];
         foreach ($currentUrlRewrites as $rewrite) {
-            if ($rewrite->getIsAutogenerated()) {
-                $urlRewrites = array_merge($urlRewrites, $this->generateForAutogenerated($rewrite, $storeId));
-            } else {
-                $urlRewrites = array_merge($urlRewrites, $this->generateForCustom($rewrite, $storeId));
-            }
+            $mergeDataProvider->merge(
+                $rewrite->getIsAutogenerated()
+                ? $this->generateForAutogenerated($rewrite, $storeId, $category)
+                : $this->generateForCustom($rewrite, $storeId, $category)
+            );
         }
-        return $urlRewrites;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url
      * @param int $storeId
-     * @return array
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForAutogenerated($url, $storeId)
+    protected function generateForAutogenerated($url, $storeId, \Magento\Catalog\Model\Category $category = null)
     {
-        $urls = [];
-        if ($this->category->getData('save_rewrites_history')) {
-            $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId);
+        if ($category->getData('save_rewrites_history')) {
+            $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId);
             if ($url->getRequestPath() !== $targetPath) {
-                $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create()
-                    ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
-                    ->setEntityId($this->category->getId())
+                $generatedUrl = clone $this->urlRewritePrototype;
+                $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
+                    ->setEntityId($category->getEntityId())
                     ->setRequestPath($url->getRequestPath())
                     ->setTargetPath($targetPath)
                     ->setRedirectType(OptionProvider::PERMANENT)
                     ->setStoreId($storeId)
                     ->setIsAutogenerated(0);
+                return [$generatedUrl];
             }
         }
-        return $urls;
+        return [];
     }
 
     /**
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url
      * @param int $storeId
-     * @return array
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForCustom($url, $storeId)
+    protected function generateForCustom($url, $storeId, \Magento\Catalog\Model\Category $category = null)
     {
-        $urls = [];
         $targetPath = !$url->getRedirectType()
             ? $url->getTargetPath()
-            : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId);
+            : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId);
         if ($url->getRequestPath() !== $targetPath) {
-            $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create()
-                ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->category->getId())
+            $generatedUrl = clone $this->urlRewritePrototype;
+            $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
+                ->setEntityId($category->getEntityId())
                 ->setRequestPath($url->getRequestPath())
                 ->setTargetPath($targetPath)
                 ->setRedirectType($url->getRedirectType())
@@ -118,7 +141,8 @@ class CurrentUrlRewritesRegenerator
                 ->setDescription($url->getDescription())
                 ->setIsAutogenerated(0)
                 ->setMetadata($url->getMetadata());
+            return [$generatedUrl];
         }
-        return $urls;
+        return [];
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php
index e44aa9e0fab09d557544f413994b907dd0184f9a..2617f3290ee0ec801fb1be73e274cc94f13af8b9 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php
@@ -5,52 +5,55 @@
  */
 namespace Magento\CatalogUrlRewrite\Model\Category\Plugin\Category;
 
-use Magento\Catalog\Api\Data\CategoryInterface;
-use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
-use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
-use Magento\UrlRewrite\Model\UrlPersistInterface;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
 
 class Remove
 {
-    /** @var UrlPersistInterface */
+    /** @var \Magento\UrlRewrite\Model\UrlPersistInterface */
     protected $urlPersist;
 
-    /** @var ProductUrlRewriteGenerator */
+    /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator */
     protected $productUrlRewriteGenerator;
 
-    /** @var ChildrenCategoriesProvider */
+    /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider */
     protected $childrenCategoriesProvider;
 
+    /** @var \Magento\Framework\Serialize\Serializer\Json */
+    private $serializer;
+
     /**
-     * @param UrlPersistInterface $urlPersist
-     * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator
-     * @param ChildrenCategoriesProvider $childrenCategoriesProvider
+     * @param \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist
+     * @param \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator
+     * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider
+     * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
      */
     public function __construct(
-        UrlPersistInterface $urlPersist,
-        ProductUrlRewriteGenerator $productUrlRewriteGenerator,
-        ChildrenCategoriesProvider $childrenCategoriesProvider
+        \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist,
+        \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator,
+        \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider,
+        \Magento\Framework\Serialize\Serializer\Json $serializer = null
     ) {
         $this->urlPersist = $urlPersist;
         $this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
         $this->childrenCategoriesProvider = $childrenCategoriesProvider;
+        $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+            \Magento\Framework\Serialize\Serializer\Json::class
+        );
     }
 
     /**
      * Remove product urls from storage
      *
      * @param \Magento\Catalog\Model\ResourceModel\Category $subject
-     * @param callable $proceed
-     * @param CategoryInterface $category
+     * @param \Closure $proceed
+     * @param \Magento\Catalog\Api\Data\CategoryInterface $category
      * @return mixed
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function aroundDelete(
         \Magento\Catalog\Model\ResourceModel\Category $subject,
         \Closure $proceed,
-        CategoryInterface $category
+        \Magento\Catalog\Api\Data\CategoryInterface $category
     ) {
         $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true);
         $categoryIds[] = $category->getId();
@@ -72,13 +75,13 @@ class Remove
         $this->urlPersist->deleteByData(
             [
                 UrlRewrite::ENTITY_ID => $categoryId,
-                UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
+                UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::ENTITY_TYPE,
             ]
         );
         $this->urlPersist->deleteByData(
             [
-                UrlRewrite::METADATA => serialize(['category_id' => $categoryId]),
-                UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+                UrlRewrite::METADATA => $this->serializer->serialize(['category_id' => $categoryId]),
+                UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE,
             ]
         );
     }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
index 709baf9e730f9a4ce0dab84799d71b7d39071406..db4b4951a8c8120cc27e6a162399414f9e62c5c4 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
@@ -5,30 +5,30 @@
  */
 namespace Magento\CatalogUrlRewrite\Model\Category\Plugin;
 
-use Magento\CatalogUrlRewrite\Model\Category\ProductFactory;
 use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
 use Magento\UrlRewrite\Model\StorageInterface;
 use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product;
 
 class Storage
 {
     /** @var UrlFinderInterface */
-    protected $urlFinder;
+    private $urlFinder;
 
-    /** @var ProductFactory */
-    protected $productFactory;
+    /** @var Product */
+    private $productResource;
 
     /**
      * @param UrlFinderInterface $urlFinder
-     * @param ProductFactory $productFactory
+     * @param Product $productResource
      */
     public function __construct(
         UrlFinderInterface $urlFinder,
-        ProductFactory $productFactory
+        Product $productResource
     ) {
         $this->urlFinder = $urlFinder;
-        $this->productFactory = $productFactory;
+        $this->productResource = $productResource;
     }
 
     /**
@@ -50,7 +50,7 @@ class Storage
             ];
         }
         if ($toSave) {
-            $this->productFactory->create()->getResource()->saveMultiple($toSave);
+            $this->productResource->saveMultiple($toSave);
         }
     }
 
@@ -62,14 +62,7 @@ class Storage
      */
     public function beforeDeleteByData(StorageInterface $object, array $data)
     {
-        $toRemove = [];
-        $records = $this->urlFinder->findAllByData($data);
-        foreach ($records as $record) {
-            $toRemove[] = $record->getUrlRewriteId();
-        }
-        if ($toRemove) {
-            $this->productFactory->create()->getResource()->removeMultiple($toRemove);
-        }
+        $this->productResource->removeMultipleByProductCategory($data);
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
index b3feb4bfd9caa7cec9b29585dbebb3dfcf7bef48..7e5d22399e91f04428ecc60f0503bff70aae0649 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
@@ -35,7 +35,7 @@ class Group
     /** @var ProductUrlRewriteGenerator */
     protected $productUrlRewriteGenerator;
 
-    /** @var StoreManagerInterface  */
+    /** @var StoreManagerInterface */
     protected $storeManager;
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
index bc09d90d461c4862908587f40915be325096a706..e40aa612002693fe50158e5543fee1858ad68fc8 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
@@ -7,11 +7,6 @@ namespace Magento\CatalogUrlRewrite\Model;
 
 use Magento\Catalog\Model\Category;
 use Magento\Catalog\Model\Product;
-use Magento\CatalogUrlRewrite\Model\Product\CanonicalUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\Product\CategoriesUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator;
-use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Catalog\Model\Product\Visibility;
 
@@ -28,7 +23,7 @@ class CategoryBasedProductRewriteGenerator
     private $productScopeRewriteGenerator;
 
     /**
-     * @param ProductScopeRewriteGenerator $productUrlRewriteGenerator
+     * @param ProductScopeRewriteGenerator $productScopeRewriteGenerator
      */
     public function __construct(
         ProductScopeRewriteGenerator $productScopeRewriteGenerator
@@ -41,9 +36,10 @@ class CategoryBasedProductRewriteGenerator
      *
      * @param \Magento\Catalog\Model\Product $product
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate(Product $product, Category $category)
+    public function generate(Product $product, Category $category, $rootCategoryId = null)
     {
         if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
             return [];
@@ -52,10 +48,14 @@ class CategoryBasedProductRewriteGenerator
         $storeId = $product->getStoreId();
 
         $urls = $this->productScopeRewriteGenerator->isGlobalScope($storeId)
-            ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product)
-            : $this->productScopeRewriteGenerator->generateForSpecificStoreView($storeId, [$category], $product);
+            ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product, $rootCategoryId)
+            : $this->productScopeRewriteGenerator->generateForSpecificStoreView(
+                $storeId,
+                [$category],
+                $product,
+                $rootCategoryId
+            );
 
-        $this->product = null;
         return $urls;
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
index 5748b69ea3ce470d932ed94f0f7009376b64b24d..84ecb5b8a53e89880d772abefaec10356f727cfa 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
@@ -12,6 +12,8 @@ use Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator;
 use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Catalog\Api\CategoryRepositoryInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
 
 class CategoryUrlRewriteGenerator
 {
@@ -21,7 +23,10 @@ class CategoryUrlRewriteGenerator
     /** @var StoreViewService */
     protected $storeViewService;
 
-    /** @var \Magento\Catalog\Model\Category */
+    /**
+     * @var \Magento\Catalog\Model\Category
+     * @deprecated
+     */
     protected $category;
 
     /** @var \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator */
@@ -33,6 +38,9 @@ class CategoryUrlRewriteGenerator
     /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */
     protected $childrenUrlRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @var bool
      */
@@ -44,66 +52,83 @@ class CategoryUrlRewriteGenerator
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator
      * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService
      * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator,
         CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator,
         ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator,
         StoreViewService $storeViewService,
-        CategoryRepositoryInterface $categoryRepository
+        CategoryRepositoryInterface $categoryRepository,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->storeViewService = $storeViewService;
         $this->canonicalUrlRewriteGenerator = $canonicalUrlRewriteGenerator;
         $this->childrenUrlRewriteGenerator = $childrenUrlRewriteGenerator;
         $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator;
         $this->categoryRepository = $categoryRepository;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
-     * {@inheritdoc}
+     * @param \Magento\Catalog\Model\Category $category
+     * @param bool $overrideStoreUrls
+     * @param int|null $rootCategoryId
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($category, $overrideStoreUrls = false)
+    public function generate($category, $overrideStoreUrls = false, $rootCategoryId = null)
     {
-        $this->category = $category;
-        $this->overrideStoreUrls = $overrideStoreUrls;
+        if ($rootCategoryId === null) {
+            $rootCategoryId = $category->getEntityId();
+        }
 
-        $storeId = $this->category->getStoreId();
+        $storeId = $category->getStoreId();
         $urls = $this->isGlobalScope($storeId)
-            ? $this->generateForGlobalScope()
-            : $this->generateForSpecificStoreView($storeId);
+            ? $this->generateForGlobalScope($category, $overrideStoreUrls, $rootCategoryId)
+            : $this->generateForSpecificStoreView($storeId, $category, $rootCategoryId);
 
-        $this->category = null;
         return $urls;
     }
 
     /**
      * Generate list of urls for global scope
      *
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @param bool $overrideStoreUrls
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForGlobalScope()
-    {
-        $urls = [];
-        $categoryId = $this->category->getId();
-        foreach ($this->category->getStoreIds() as $storeId) {
+    protected function generateForGlobalScope(
+        Category $category = null,
+        $overrideStoreUrls = false,
+        $rootCategoryId = null
+    ) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $categoryId = $category->getId();
+        foreach ($category->getStoreIds() as $storeId) {
             if (!$this->isGlobalScope($storeId)
-                && $this->isOverrideUrlsForStore($storeId, $categoryId)
+                && $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls)
             ) {
-                $this->updateCategoryUrlForStore($storeId);
-                $urls = array_merge($urls, $this->generateForSpecificStoreView($storeId));
+                $this->updateCategoryUrlForStore($storeId, $category);
+                $mergeDataProvider->merge($this->generateForSpecificStoreView($storeId, $category, $rootCategoryId));
             }
         }
-        return $urls;
+        $result = $mergeDataProvider->getData();
+        return $result;
     }
 
     /**
      * @param int $storeId
      * @param int $categoryId
+     * @param bool $overrideStoreUrls
      * @return bool
      */
-    protected function isOverrideUrlsForStore($storeId, $categoryId)
+    protected function isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls = false)
     {
-        return $this->overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
+        return $overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
             $storeId,
             $categoryId,
             Category::ENTITY
@@ -114,12 +139,18 @@ class CategoryUrlRewriteGenerator
      * Override url key and url path for category in specific Store
      *
      * @param int $storeId
+     * @param \Magento\Catalog\Model\Category|null $category
      * @return void
      */
-    protected function updateCategoryUrlForStore($storeId)
+    protected function updateCategoryUrlForStore($storeId, Category $category = null)
     {
-        $category = $this->categoryRepository->get($this->category->getId(), $storeId);
-        $this->category->addData(['url_key' => $category->getUrlKey(), 'url_path' => $category->getUrlPath()]);
+        $categoryFromRepository = $this->categoryRepository->get($category->getId(), $storeId);
+            $category->addData(
+                [
+                    'url_key' => $categoryFromRepository->getUrlKey(),
+                    'url_path' => $categoryFromRepository->getUrlPath()
+                ]
+            );
     }
 
     /**
@@ -137,15 +168,22 @@ class CategoryUrlRewriteGenerator
      * Generate list of urls per store
      *
      * @param string $storeId
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForSpecificStoreView($storeId)
+    protected function generateForSpecificStoreView($storeId, Category $category = null, $rootCategoryId = null)
     {
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerator->generate($storeId, $this->category),
-            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category),
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $mergeDataProvider->merge(
+            $this->canonicalUrlRewriteGenerator->generate($storeId, $category)
         );
-        return $urls;
+        $mergeDataProvider->merge(
+            $this->childrenUrlRewriteGenerator->generate($storeId, $category, $rootCategoryId)
+        );
+        $mergeDataProvider->merge(
+            $this->currentUrlRewritesRegenerator->generate($storeId, $category, $rootCategoryId)
+        );
+        return $mergeDataProvider->getData();
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..668517f46d89775c32ca28d7882cba0e9d7742f7
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\CategoryFactory;
+use Magento\Catalog\Model\CategoryRepository;
+use Magento\Catalog\Api\Data\CategoryInterface;
+
+/**
+ * Map that holds data for category ids and its subcategories ids
+ */
+class DataCategoryHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var CategoryRepository */
+    private $categoryRepository;
+
+    /** @var CategoryFactory */
+    private $categoryResourceFactory;
+
+    /**
+     * @param CategoryRepository $categoryRepository
+     * @param CategoryFactory $categoryResourceFactory
+     */
+    public function __construct(
+        CategoryRepository $categoryRepository,
+        CategoryFactory $categoryResourceFactory
+    ) {
+        $this->categoryRepository = $categoryRepository;
+        $this->categoryResourceFactory = $categoryResourceFactory;
+    }
+
+    /**
+     * Returns an array of categories ids that includes category identified by $categoryId and all its subcategories
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $category = $this->categoryRepository->get($categoryId);
+            $this->hashMap[$categoryId] = $this->getAllCategoryChildrenIds($category);
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * Queries the database for sub-categories ids from a category
+     *
+     * @param CategoryInterface $category
+     * @return int[]
+     */
+    private function getAllCategoryChildrenIds(CategoryInterface $category)
+    {
+        $categoryResource = $this->categoryResourceFactory->create();
+        $connection = $categoryResource->getConnection();
+        $select = $connection->select()
+            ->from($categoryResource->getEntityTable(), 'entity_id')
+            ->where($connection->quoteIdentifier('path') . ' LIKE :c_path');
+        $bind = ['c_path' => $category->getPath() . '%'];
+        return $connection->fetchCol($select, $bind);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..032ed5e13023e1de62c7c111ed8889d7e6dab6b6
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Select;
+use Magento\UrlRewrite\Model\MergeDataProvider;
+
+/**
+ * Map that holds data for category url rewrites entity
+ */
+class DataCategoryUrlRewriteDatabaseMap implements DatabaseMapInterface
+{
+    const ENTITY_TYPE = 'category';
+
+    /** @var string[] */
+    private $createdTableAdapters = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /** @var TemporaryTableService */
+    private $temporaryTableService;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool,
+     * @param TemporaryTableService $temporaryTableService
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool,
+        TemporaryTableService $temporaryTableService
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+        $this->temporaryTableService = $temporaryTableService;
+    }
+
+    /**
+     * Generates data from categoryId and stores it into a temporary table
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    private function generateTableAdapter($categoryId)
+    {
+        if (!isset($this->createdTableAdapters[$categoryId])) {
+            $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId);
+        }
+    }
+
+    /**
+     * Queries the database for all category url rewrites that are affected by the category identified by $categoryId
+     * It returns the name of the temporary table where the resulting data is stored
+     *
+     * @param int $categoryId
+     * @return string
+     */
+    private function generateData($categoryId)
+    {
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(
+                ['e' => $this->connection->getTableName('url_rewrite')],
+                ['e.*', 'hash_key' => new \Zend_Db_Expr(
+                    "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)"
+                )
+                ]
+            )
+            ->where('entity_type = ?', self::ENTITY_TYPE)
+            ->where(
+                $urlRewritesConnection->prepareSqlCondition(
+                    'entity_id',
+                    [
+                        'in' => array_merge(
+                            $this->hashMapPool->getDataMap(DataCategoryUsedInProductsHashMap::class, $categoryId)
+                                ->getAllData($categoryId),
+                            $this->hashMapPool->getDataMap(DataCategoryHashMap::class, $categoryId)
+                                ->getAllData($categoryId)
+                        )
+                    ]
+                )
+            );
+        $mapName = $this->temporaryTableService->createFromSelect(
+            $select,
+            $this->connection->getConnection(),
+            [
+                'PRIMARY' => ['url_rewrite_id'],
+                'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                'ENTITY_STORE' => ['entity_id', 'store_id']
+            ]
+        );
+        return $mapName;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroyTableAdapter($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataCategoryUsedInProductsHashMap::class, $categoryId);
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        if (isset($this->createdTableAdapters[$categoryId])) {
+            $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]);
+            unset($this->createdTableAdapters[$categoryId]);
+        }
+    }
+
+    /**
+     * Gets data by criteria from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key)
+    {
+        $this->generateTableAdapter($categoryId);
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()->from(['e' => $this->createdTableAdapters[$categoryId]]);
+        if (strlen($key) > 0) {
+            $select->where('hash_key = ?', $key);
+        }
+
+        return $urlRewritesConnection->fetchAll($select);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..65200ba1f413d63f06f0d9b319fb6a339d55ae43
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Map that holds data for categories used by products found in root category
+ */
+class DataCategoryUsedInProductsHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+    }
+
+    /**
+     * Returns an array of product ids for all DataProductHashMap list,
+     * that occur in other categories not part of DataCategoryHashMap list
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $productsLinkConnection = $this->connection->getConnection();
+            $select = $productsLinkConnection->select()
+                ->from($this->connection->getTableName('catalog_category_product'), ['category_id'])
+                ->where(
+                    $productsLinkConnection->prepareSqlCondition(
+                        'product_id',
+                        [
+                            'in' => $this->hashMapPool->getDataMap(
+                                DataProductHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )
+                ->where(
+                    $productsLinkConnection->prepareSqlCondition(
+                        'category_id',
+                        [
+                            'nin' => $this->hashMapPool->getDataMap(
+                                DataCategoryHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )->group('category_id');
+
+            $this->hashMap[$categoryId] = $productsLinkConnection->fetchCol($select);
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId);
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b107043459a3c041181961f5cb0a9c02eeb1a41
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Map that holds data for products ids from a category and subcategories
+ */
+class DataProductHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var CollectionFactory */
+    private $collectionFactory;
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /**
+     * @param CollectionFactory $collectionFactory
+     * @param HashMapPool $hashMapPool
+     * @param ResourceConnection $connection
+     */
+    public function __construct(
+        CollectionFactory $collectionFactory,
+        HashMapPool $hashMapPool,
+        ResourceConnection $connection
+    ) {
+        $this->collectionFactory = $collectionFactory;
+        $this->hashMapPool = $hashMapPool;
+        $this->connection = $connection;
+    }
+
+    /**
+     * Returns an array of ids of all visible products and assigned to a category and all its subcategories
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $productsCollection = $this->collectionFactory->create();
+            $productsCollection->getSelect()
+                ->joinInner(
+                    ['cp' => $this->connection->getTableName('catalog_category_product')],
+                    'cp.product_id = e.entity_id',
+                    []
+                )
+                ->where(
+                    $productsCollection->getConnection()->prepareSqlCondition(
+                        'cp.category_id',
+                        [
+                            'in' => $this->hashMapPool->getDataMap(
+                                DataCategoryHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )->group('e.entity_id');
+            $this->hashMap[$categoryId] = $productsCollection->getAllIds();
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..19c701d9445c60884be9cbab2b29839bd24a39b2
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Select;
+use Magento\UrlRewrite\Model\MergeDataProvider;
+
+/**
+ * Map that holds data for category url rewrites entity
+ */
+class DataProductUrlRewriteDatabaseMap implements DatabaseMapInterface
+{
+    const ENTITY_TYPE = 'product';
+
+    /** @var string[] */
+    private $createdTableAdapters = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /** @var TemporaryTableService */
+    private $temporaryTableService;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool,
+     * @param TemporaryTableService $temporaryTableService
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool,
+        TemporaryTableService $temporaryTableService
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+        $this->temporaryTableService = $temporaryTableService;
+    }
+
+    /**
+     * Generates data from categoryId and stores it into a temporary table
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    private function generateTableAdapter($categoryId)
+    {
+        if (!isset($this->createdTableAdapters[$categoryId])) {
+            $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $this->generateTableAdapter($categoryId);
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(['e' => $this->createdTableAdapters[$categoryId]])
+            ->where('hash_key = ?', $key);
+        return $urlRewritesConnection->fetchAll($select);
+    }
+
+    /**
+     * Queries the database for all category url rewrites that are affected by the category identified by $categoryId
+     * It returns the name of the temporary table where the resulting data is stored
+     *
+     * @param int $categoryId
+     * @return string
+     */
+    private function generateData($categoryId)
+    {
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(
+                ['e' => $this->connection->getTableName('url_rewrite')],
+                ['e.*', 'hash_key' => new \Zend_Db_Expr(
+                    "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)"
+                )
+                ]
+            )
+            ->where('entity_type = ?', self::ENTITY_TYPE)
+            ->where(
+                $urlRewritesConnection->prepareSqlCondition(
+                    'entity_id',
+                    [
+                        'in' => $this->hashMapPool->getDataMap(DataProductHashMap::class, $categoryId)
+                            ->getAllData($categoryId)
+                    ]
+                )
+            );
+        $mapName = $this->temporaryTableService->createFromSelect(
+            $select,
+            $this->connection->getConnection(),
+            [
+                'PRIMARY' => ['url_rewrite_id'],
+                'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                'ENTITY_STORE' => ['entity_id', 'store_id']
+            ]
+        );
+        return $mapName;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroyTableAdapter($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId);
+        if (isset($this->createdTableAdapters[$categoryId])) {
+            $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]);
+            unset($this->createdTableAdapters[$categoryId]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2a9a89a33fb5dc689886e5e071c391f99eadd01
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\DB\Select;
+
+/**
+ * Interface for a mysql data type of a map
+ *
+ * Is used to get data by a unique key from a temporary table in mysql to prevent memory usage
+ * It internally holds the knowledge the creation of the actual data and it initializes itself when we call getData
+ * We should always call destroyTableAdapter when we don't need anymore the temporary tables
+ */
+interface DatabaseMapInterface
+{
+    /**
+     * Gets data by key from a map identified by a category Id
+     *
+     * The key is a unique identifier that matches the values of the index used to build the temporary table
+     *
+     * Example "1_2" where ids would correspond to store_id entity_id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key);
+
+    /**
+     * Destroys data in the temporary table by categoryId
+     * It also destroys the data in other maps that are dependencies used to construct the data
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    public function destroyTableAdapter($categoryId);
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb94c1972a17c4e4afa1db555fc461cc21625c95
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Pool for database maps
+ */
+class DatabaseMapPool
+{
+    /**
+     * @var DatabaseMapInterface[]
+     */
+    private $dataArray = [];
+
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * Constructor
+     *
+     * @param ObjectManagerInterface $objectManager
+     */
+    public function __construct(
+        ObjectManagerInterface $objectManager
+    ) {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * Gets a map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return DatabaseMapInterface
+     */
+    public function getDataMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (!isset($this->dataArray[$key])) {
+            $instance = $this->objectManager->create(
+                $instanceName,
+                [
+                    'category' => $categoryId
+                ]
+            );
+            if (!$instance instanceof DatabaseMapInterface) {
+                throw new \InvalidArgumentException(
+                    $instanceName . ' does not implement interface ' . DatabaseMapInterface::class
+                );
+            }
+            $this->dataArray[$key] = $instance;
+        }
+        return $this->dataArray[$key];
+    }
+
+    /**
+     * Resets a database map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (isset($this->dataArray[$key])) {
+            $this->dataArray[$key]->destroyTableAdapter($categoryId);
+            unset($this->dataArray[$key]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..341cdb1a4fdb1aba3375bace3a687e9d7fd273ee
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\DB\Select;
+
+/**
+ * Interface for a hash data map
+ *
+ * It is used for classes that will build hash maps and store them into memory
+ * The initialization is done transparently whenever getAllData or getData is called
+ * The map, upon initialization, might have a dependency on some other DataMapInterfaces
+ * The map has to free memory after we're done using it
+ * We need to destroy those maps too when calling resetData
+ */
+interface HashMapInterface
+{
+    /**
+     * Gets all data from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId);
+
+    /**
+     * Gets data by criteria from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key);
+
+    /**
+     * Resets current map by freeing memory and also to its dependencies
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetData($categoryId);
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php
new file mode 100644
index 0000000000000000000000000000000000000000..6606812a61995fa24a383170e67c6c99eb1caa90
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Pool for hash maps
+ */
+class HashMapPool
+{
+    /**
+     * @var HashMapInterface[]
+     */
+    private $dataArray = [];
+
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * Constructor
+     *
+     * @param ObjectManagerInterface $objectManager
+     */
+    public function __construct(
+        ObjectManagerInterface $objectManager
+    ) {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * Gets a map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return HashMapInterface
+     * @throws \Exception
+     */
+    public function getDataMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (!isset($this->dataArray[$key])) {
+            $instance = $this->objectManager->create(
+                $instanceName,
+                [
+                    'category' => $categoryId
+                ]
+            );
+            if (!$instance instanceof HashMapInterface) {
+                throw new \InvalidArgumentException(
+                    $instanceName . ' does not implement interface ' . HashMapInterface::class
+                );
+            }
+            $this->dataArray[$key] = $instance;
+        }
+        return $this->dataArray[$key];
+    }
+
+    /**
+     * Resets data in a hash map by instance name and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (isset($this->dataArray[$key])) {
+            $this->dataArray[$key]->resetData($categoryId);
+            unset($this->dataArray[$key]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcadfd848e8830461c380b86aeb32bdc116a6a96
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\Product;
+use Magento\UrlRewrite\Model\UrlFinderInterface;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
+
+/**
+ * Finds specific queried url rewrites identified by specific fields
+ *
+ * A group of identifiers specifies a query consumed by the client to retrieve existing url rewrites from the database
+ * Clients will query a map of DatabaseMapInterface type through this class resulting into a set of url rewrites results
+ * Each map type will fallback to a UrlFinderInterface by identifiers for unmapped values
+ */
+class UrlRewriteFinder
+{
+    const ENTITY_TYPE_CATEGORY = 'category';
+    const ENTITY_TYPE_PRODUCT = 'product';
+
+    /** @var DatabaseMapPool */
+    private $databaseMapPool;
+
+    /** @var UrlFinderInterface */
+    private $urlFinder;
+
+    /** @var UrlRewrite */
+    private $urlRewritePrototype;
+
+    /** @var array */
+    private $urlRewriteClassNames = [];
+
+    /**
+     * @param DatabaseMapPool $databaseMapPool
+     * @param UrlFinderInterface $urlFinder
+     * @param UrlRewriteFactory $urlRewriteFactory
+     * @param string[] $urlRewriteClassNames
+     */
+    public function __construct(
+        DatabaseMapPool $databaseMapPool,
+        UrlFinderInterface $urlFinder,
+        UrlRewriteFactory $urlRewriteFactory,
+        array $urlRewriteClassNames = []
+    ) {
+        $this->databaseMapPool = $databaseMapPool;
+        $this->urlFinder = $urlFinder;
+        $this->urlRewriteClassNames = $urlRewriteClassNames;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
+    }
+
+    /**
+     * Retrieves existing url rewrites filtered by identifiers from prebuild database maps
+     * This method will fall-back to by using UrlFinderInterface when map type is not found in configured list
+     *
+     * @param int $entityId
+     * @param int $storeId
+     * @param string $entityType
+     * @param int|null $rootCategoryId
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
+     */
+    public function findAllByData($entityId, $storeId, $entityType, $rootCategoryId = null)
+    {
+        if ($rootCategoryId
+            && is_numeric($entityId)
+            && is_numeric($storeId)
+            && is_string($entityType)
+            && isset($this->urlRewriteClassNames[$entityType])
+        ) {
+            $map = $this->databaseMapPool->getDataMap($this->urlRewriteClassNames[$entityType], $rootCategoryId);
+            if ($map) {
+                $key = $storeId . '_' . $entityId;
+                return $this->arrayToUrlRewriteObject($map->getData($rootCategoryId, $key));
+            }
+        }
+
+        return $this->urlFinder->findAllByData(
+            [
+                UrlRewrite::STORE_ID => $storeId,
+                UrlRewrite::ENTITY_ID => $entityId,
+                UrlRewrite::ENTITY_TYPE => $entityType
+            ]
+        );
+    }
+
+    /**
+     * Transfers an array values to url rewrite object values
+     *
+     * @param array $data
+     * @return UrlRewrite[]
+     */
+    private function arrayToUrlRewriteObject(array $data)
+    {
+        foreach ($data as $key => $array) {
+            $data[$key] = $this->createUrlRewrite($array);
+        }
+        return $data;
+    }
+
+    /**
+     * Creates url rewrite object and sets $data to its properties by key->value
+     *
+     * @param array $data
+     * @return UrlRewrite
+     */
+    private function createUrlRewrite(array $data)
+    {
+        $dataObject = clone $this->urlRewritePrototype;
+        $dataObject->setUrlRewriteId($data['url_rewrite_id']);
+        $dataObject->setEntityType($data['entity_type']);
+        $dataObject->setEntityId($data['entity_id']);
+        $dataObject->setRequestPath($data['request_path']);
+        $dataObject->setTargetPath($data['target_path']);
+        $dataObject->setRedirectType($data['redirect_type']);
+        $dataObject->setStoreId($data['store_id']);
+        $dataObject->setDescription($data['description']);
+        $dataObject->setIsAutogenerated($data['is_autogenerated']);
+        $dataObject->setMetadata($data['metadata']);
+
+        return $dataObject;
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
index 5f1f01f7b08a83222dd84df95838b69e0031a079..dee95f236841ef6c3c4ceb5e32eaf4de8cdda238 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
@@ -14,20 +14,31 @@ use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class CurrentUrlRewritesRegenerator
 {
-    /** @var Product */
+    /**
+     * @var Product
+     * @deprecated
+     */
     protected $product;
 
-    /** @var ObjectRegistry */
+    /**
+     * @var ObjectRegistry
+     * @deprecated
+     */
     protected $productCategories;
 
-    /** @var UrlFinderInterface */
+    /**
+     * @var UrlFinderInterface
+     * @deprecated
+     */
     protected $urlFinder;
 
     /** @var ProductUrlPathGenerator */
@@ -36,19 +47,38 @@ class CurrentUrlRewritesRegenerator
     /** @var UrlRewriteFactory */
     protected $urlRewriteFactory;
 
+    /** @var UrlRewrite */
+    private $urlRewritePrototype;
+
+    /** @var UrlRewriteFinder */
+    private $urlRewriteFinder;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param UrlFinderInterface $urlFinder
      * @param ProductUrlPathGenerator $productUrlPathGenerator
      * @param UrlRewriteFactory $urlRewriteFactory
+     * @param UrlRewriteFinder|null $urlRewriteFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         UrlFinderInterface $urlFinder,
         ProductUrlPathGenerator $productUrlPathGenerator,
-        UrlRewriteFactory $urlRewriteFactory
+        UrlRewriteFactory $urlRewriteFactory,
+        UrlRewriteFinder $urlRewriteFinder = null,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->urlFinder = $urlFinder;
         $this->productUrlPathGenerator = $productUrlPathGenerator;
         $this->urlRewriteFactory = $urlRewriteFactory;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
+        $this->urlRewriteFinder = $urlRewriteFinder ?: ObjectManager::getInstance()->get(UrlRewriteFinder::class);
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -57,104 +87,100 @@ class CurrentUrlRewritesRegenerator
      * @param int $storeId
      * @param Product $product
      * @param ObjectRegistry $productCategories
+     * @param int|null $rootCategoryId
      * @return UrlRewrite[]
      */
-    public function generate($storeId, Product $product, ObjectRegistry $productCategories)
+    public function generate($storeId, Product $product, ObjectRegistry $productCategories, $rootCategoryId = null)
     {
-        $this->product = $product;
-        $this->productCategories = $productCategories;
-
-        $currentUrlRewrites = $this->urlFinder->findAllByData(
-            [
-                UrlRewrite::STORE_ID => $storeId,
-                UrlRewrite::ENTITY_ID => $this->product->getId(),
-                UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
-            ]
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $currentUrlRewrites = $this->urlRewriteFinder->findAllByData(
+            $product->getEntityId(),
+            $storeId,
+            ProductUrlRewriteGenerator::ENTITY_TYPE,
+            $rootCategoryId
         );
 
-        $urlRewrites = [];
         foreach ($currentUrlRewrites as $currentUrlRewrite) {
-            $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite);
+            $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite, $productCategories);
             if ($category === false) {
                 continue;
             }
-            $url = $currentUrlRewrite->getIsAutogenerated()
-                ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category)
-                : $this->generateForCustom($currentUrlRewrite, $storeId, $category);
-            $urlRewrites = array_merge($urlRewrites, $url);
+            $mergeDataProvider->merge(
+                $currentUrlRewrite->getIsAutogenerated()
+                ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category, $product)
+                : $this->generateForCustom($currentUrlRewrite, $storeId, $category, $product)
+            );
         }
 
-        $this->product = null;
-        $this->productCategories = null;
-        return $urlRewrites;
+        return $mergeDataProvider->getData();
     }
 
     /**
      * @param UrlRewrite $url
      * @param int $storeId
      * @param Category|null $category
-     * @return array
+     * @param Product|null $product
+     * @return UrlRewrite[]
      */
-    protected function generateForAutogenerated($url, $storeId, $category)
+    protected function generateForAutogenerated($url, $storeId, $category, $product = null)
     {
-        if (!$this->product->getData('save_rewrites_history')) {
-            return [];
-        }
-        $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category);
-        if ($url->getRequestPath() === $targetPath) {
-            return [];
+        if ($product->getData('save_rewrites_history')) {
+            $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category);
+            if ($url->getRequestPath() !== $targetPath) {
+                $generatedUrl = clone $this->urlRewritePrototype;
+                $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
+                    ->setEntityId($product->getEntityId())
+                    ->setRequestPath($url->getRequestPath())
+                    ->setTargetPath($targetPath)
+                    ->setRedirectType(OptionProvider::PERMANENT)
+                    ->setStoreId($storeId)
+                    ->setDescription($url->getDescription())
+                    ->setIsAutogenerated(0)
+                    ->setMetadata($url->getMetadata());
+                return [$generatedUrl];
+            }
         }
-        return [
-            $this->urlRewriteFactory->create()
-                ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->product->getId())
-                ->setRequestPath($url->getRequestPath())
-                ->setTargetPath($targetPath)
-                ->setRedirectType(OptionProvider::PERMANENT)
-                ->setStoreId($storeId)
-                ->setDescription($url->getDescription())
-                ->setIsAutogenerated(0)
-                ->setMetadata($url->getMetadata())
-        ];
+        return [];
     }
 
     /**
      * @param UrlRewrite $url
      * @param int $storeId
      * @param Category|null $category
-     * @return array
+     * @param Product|null $product
+     * @return UrlRewrite[]
      */
-    protected function generateForCustom($url, $storeId, $category)
+    protected function generateForCustom($url, $storeId, $category, $product = null)
     {
         $targetPath = $url->getRedirectType()
-            ? $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category)
+            ? $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category)
             : $url->getTargetPath();
-        if ($url->getRequestPath() === $targetPath) {
-            return [];
-        }
-        return [
-            $this->urlRewriteFactory->create()
-                ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->product->getId())
+        if ($url->getRequestPath() !== $targetPath) {
+            $generatedUrl = clone $this->urlRewritePrototype;
+            $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
+                ->setEntityId($product->getEntityId())
                 ->setRequestPath($url->getRequestPath())
                 ->setTargetPath($targetPath)
                 ->setRedirectType($url->getRedirectType())
                 ->setStoreId($storeId)
                 ->setDescription($url->getDescription())
                 ->setIsAutogenerated(0)
-                ->setMetadata($url->getMetadata())
-        ];
+                ->setMetadata($url->getMetadata());
+            return [$generatedUrl];
+        }
+        return [];
     }
 
     /**
      * @param UrlRewrite $url
+     * @param ObjectRegistry|null $productCategories
      * @return Category|null|bool
      */
-    protected function retrieveCategoryFromMetadata($url)
+    protected function retrieveCategoryFromMetadata($url, ObjectRegistry $productCategories = null)
     {
         $metadata = $url->getMetadata();
         if (isset($metadata['category_id'])) {
-            $category = $this->productCategories->get($metadata['category_id']);
+            $category = $productCategories->get($metadata['category_id']);
             return $category === null ? false : $category;
         }
         return null;
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
index 5383709ade6e0d250a4086d6adc2b4a7bb6e1f4b..6e357459de8d3790c9e57624c1a7dbb1a9910268 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
@@ -14,6 +14,8 @@ use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Store\Model\StoreManagerInterface;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+use Magento\Framework\App\ObjectManager;
 
 /**
  * Class ProductScopeRewriteGenerator
@@ -56,6 +58,9 @@ class ProductScopeRewriteGenerator
      */
     private $canonicalUrlRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param StoreViewService $storeViewService
      * @param StoreManagerInterface $storeManager
@@ -64,6 +69,7 @@ class ProductScopeRewriteGenerator
      * @param CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator
      * @param CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator
      * @param AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         StoreViewService $storeViewService,
@@ -72,7 +78,8 @@ class ProductScopeRewriteGenerator
         CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator,
         CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator,
         CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator,
-        AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator
+        AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->storeViewService = $storeViewService;
         $this->storeManager = $storeManager;
@@ -81,6 +88,10 @@ class ProductScopeRewriteGenerator
         $this->categoriesUrlRewriteGenerator = $categoriesUrlRewriteGenerator;
         $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator;
         $this->anchorUrlRewriteGenerator = $anchorUrlRewriteGenerator;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -97,36 +108,44 @@ class ProductScopeRewriteGenerator
     /**
      * Generate url rewrites for global scope
      *
+     * @param \Magento\Framework\Data\Collection|\Magento\Catalog\Model\Category[] $productCategories
      * @param Product $product
-     * @param \Magento\Framework\Data\Collection $productCategories
+     * @param int|null $rootCategoryId
      * @return array
      */
-    public function generateForGlobalScope($productCategories, Product $product)
+    public function generateForGlobalScope($productCategories, Product $product, $rootCategoryId = null)
     {
-        $urls = [];
         $productId = $product->getEntityId();
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
 
         foreach ($product->getStoreIds() as $id) {
-            if (!$this->isGlobalScope($id)
-                && !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore($id, $productId, Product::ENTITY)
-            ) {
-                $urls = array_merge($urls, $this->generateForSpecificStoreView($id, $productCategories, $product));
+            if (!$this->isGlobalScope($id) &&
+                !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
+                    $id,
+                    $productId,
+                    Product::ENTITY
+                )) {
+                $mergeDataProvider->merge(
+                    $this->generateForSpecificStoreView($id, $productCategories, $product, $rootCategoryId)
+                );
             }
         }
 
-        return $urls;
+        return $mergeDataProvider->getData();
     }
 
     /**
      * Generate list of urls for specific store view
      *
      * @param int $storeId
-     * @param \Magento\Framework\Data\Collection $productCategories
+     * @param \Magento\Framework\Data\Collection|Category[] $productCategories
      * @param \Magento\Catalog\Model\Product $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generateForSpecificStoreView($storeId, $productCategories, Product $product)
+    public function generateForSpecificStoreView($storeId, $productCategories, Product $product, $rootCategoryId = null)
     {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         $categories = [];
         foreach ($productCategories as $category) {
             if ($this->isCategoryProperForGenerating($category, $storeId)) {
@@ -134,23 +153,26 @@ class ProductScopeRewriteGenerator
             }
         }
         $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]);
-        /**
-         * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
-         */
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerator->generate($storeId, $product),
-            $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories),
-            $this->currentUrlRewritesRegenerator->generate($storeId, $product, $productCategories),
+
+        $mergeDataProvider->merge(
+            $this->canonicalUrlRewriteGenerator->generate($storeId, $product)
+        );
+        $mergeDataProvider->merge(
+            $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories)
+        );
+        $mergeDataProvider->merge(
+            $this->currentUrlRewritesRegenerator->generate(
+                $storeId,
+                $product,
+                $productCategories,
+                $rootCategoryId
+            )
+        );
+        $mergeDataProvider->merge(
             $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories)
         );
 
-        /* Reduce duplicates. Last wins */
-        $result = [];
-        foreach ($urls as $url) {
-            $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url;
-        }
-        $this->productCategories = null;
-        return $result;
+        return $mergeDataProvider->getData();
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
index 0ff4877405477f163060443ef08de7bd71c2d4bb..a45b33bcc52a2570030c83a8b3052808a9aad144 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
@@ -31,7 +31,10 @@ class ProductUrlRewriteGenerator
      */
     protected $storeViewService;
 
-    /** @var \Magento\Catalog\Model\Product */
+    /**
+     * @var \Magento\Catalog\Model\Product
+     * @deprecated
+     */
     protected $product;
 
     /**
@@ -119,26 +122,25 @@ class ProductUrlRewriteGenerator
      * Generate product url rewrites
      *
      * @param \Magento\Catalog\Model\Product $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate(Product $product)
+    public function generate(Product $product, $rootCategoryId = null)
     {
         if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
             return [];
         }
 
-        $this->product = $product;
-        $storeId = $this->product->getStoreId();
+        $storeId = $product->getStoreId();
 
         $productCategories = $product->getCategoryCollection()
             ->addAttributeToSelect('url_key')
             ->addAttributeToSelect('url_path');
 
         $urls = $this->isGlobalScope($storeId)
-            ? $this->generateForGlobalScope($productCategories)
-            : $this->generateForSpecificStoreView($storeId, $productCategories);
+            ? $this->generateForGlobalScope($productCategories, $product, $rootCategoryId)
+            : $this->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId);
 
-        $this->product = null;
         return $urls;
     }
 
@@ -159,11 +161,17 @@ class ProductUrlRewriteGenerator
      *
      * @deprecated
      * @param \Magento\Framework\Data\Collection $productCategories
+     * @param \Magento\Catalog\Model\Product|null $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForGlobalScope($productCategories)
+    protected function generateForGlobalScope($productCategories, $product = null, $rootCategoryId = null)
     {
-        return $this->getProductScopeRewriteGenerator()->generateForGlobalScope($productCategories, $this->product);
+        return $this->getProductScopeRewriteGenerator()->generateForGlobalScope(
+            $productCategories,
+            $product,
+            $rootCategoryId
+        );
     }
 
     /**
@@ -172,12 +180,18 @@ class ProductUrlRewriteGenerator
      * @deprecated
      * @param int $storeId
      * @param \Magento\Framework\Data\Collection $productCategories
+     * @param Product|null $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForSpecificStoreView($storeId, $productCategories)
-    {
+    protected function generateForSpecificStoreView(
+        $storeId,
+        $productCategories,
+        $product = null,
+        $rootCategoryId = null
+    ) {
         return $this->getProductScopeRewriteGenerator()
-            ->generateForSpecificStoreView($storeId, $productCategories, $this->product);
+            ->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId);
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
index f2043d305c4e8545e1d01e4da7cf0c9c0f511640..ead44dca8a0df9040de8f4e2b1f8c710e064518e 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
@@ -6,6 +6,7 @@
 namespace Magento\CatalogUrlRewrite\Model\ResourceModel\Category;
 
 use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
+use Magento\UrlRewrite\Model\Storage\DbStorage;
 
 class Product extends AbstractDb
 {
@@ -55,6 +56,8 @@ class Product extends AbstractDb
     }
 
     /**
+     * Removes data by primary key
+     *
      * @param array $removeData
      * @return int
      */
@@ -65,4 +68,36 @@ class Product extends AbstractDb
             ['url_rewrite_id in (?)' => $removeData]
         );
     }
+
+    /**
+     * Removes multiple entities from url_rewrite table using entities from catalog_url_rewrite_product_category
+     * Example: $filter = ['category_id' => [1, 2, 3], 'product_id' => [1, 2, 3]]
+     *
+     * @param array $filter
+     * @return int
+     */
+    public function removeMultipleByProductCategory(array $filter)
+    {
+        return $this->getConnection()->delete(
+            $this->getTable(self::TABLE_NAME),
+            ['url_rewrite_id in (?)' => $this->prepareSelect($filter)]
+        );
+    }
+
+    /**
+     * Prepare select statement for specific filter
+     *
+     * @param array $data
+     * @return \Magento\Framework\DB\Select
+     */
+    private function prepareSelect($data)
+    {
+        $select = $this->getConnection()->select();
+        $select->from($this->getTable(DbStorage::TABLE_NAME), 'url_rewrite_id');
+
+        foreach ($data as $column => $value) {
+            $select->where($this->getConnection()->quoteIdentifier($column) . ' IN (?)', $value);
+        }
+        return $select;
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index a4847fa11b1c40911d08831adbf86553d39fc90f..f76db7bef44d5b81936015bb033990c36205bc54 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -20,6 +20,9 @@ use Magento\UrlRewrite\Model\OptionProvider;
 use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\Framework\Event\ObserverInterface;
 use Magento\Catalog\Model\Product\Visibility;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+
 /**
  * Class AfterImportDataObserver
  *
@@ -96,6 +99,9 @@ class AfterImportDataObserver implements ObserverInterface
         'visibility',
     ];
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\Catalog\Model\ProductFactory $catalogProductFactory
      * @param \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory
@@ -105,6 +111,7 @@ class AfterImportDataObserver implements ObserverInterface
      * @param UrlPersistInterface $urlPersist
      * @param UrlRewriteFactory $urlRewriteFactory
      * @param UrlFinderInterface $urlFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      * @throws \InvalidArgumentException
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
@@ -116,7 +123,8 @@ class AfterImportDataObserver implements ObserverInterface
         \Magento\Store\Model\StoreManagerInterface $storeManager,
         UrlPersistInterface $urlPersist,
         UrlRewriteFactory $urlRewriteFactory,
-        UrlFinderInterface $urlFinder
+        UrlFinderInterface $urlFinder,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->urlPersist = $urlPersist;
         $this->catalogProductFactory = $catalogProductFactory;
@@ -126,6 +134,10 @@ class AfterImportDataObserver implements ObserverInterface
         $this->storeManager = $storeManager;
         $this->urlRewriteFactory = $urlRewriteFactory;
         $this->urlFinder = $urlFinder;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -258,24 +270,16 @@ class AfterImportDataObserver implements ObserverInterface
      */
     protected function generateUrls()
     {
-        /**
-         * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
-         */
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerate(),
-            $this->categoriesUrlRewriteGenerate(),
-            $this->currentUrlRewritesRegenerate()
-        );
-
-        /* Reduce duplicates. Last wins */
-        $result = [];
-        foreach ($urls as $url) {
-            $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url;
-        }
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate());
+        $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate());
+        $mergeDataProvider->merge($this->currentUrlRewritesRegenerate());
         $this->productCategories = null;
 
+        unset($this->products);
         $this->products = [];
-        return $result;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
index 5a4946d50526aa6d3bb7ed3e61dcf135863cc52a..dd0250049a426187f674ac61ad60e8c78ac02f67 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
@@ -8,54 +8,50 @@ namespace Magento\CatalogUrlRewrite\Observer;
 use Magento\Catalog\Model\Category;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer;
-use Magento\Framework\App\ObjectManager;
-use Magento\UrlRewrite\Model\UrlPersistInterface;
 use Magento\Framework\Event\ObserverInterface;
+use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
 
 class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
 {
     /** @var CategoryUrlRewriteGenerator */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
 
-    /** @var UrlPersistInterface */
-    protected $urlPersist;
-
-    /**
-     * @var UrlRewriteBunchReplacer
-     */
+    /** @var UrlRewriteBunchReplacer */
     private $urlRewriteBunchReplacer;
 
     /** @var UrlRewriteHandler */
-    protected $urlRewriteHandler;
+    private $urlRewriteHandler;
+
+    /** @var DatabaseMapPool */
+    private $databaseMapPool;
+
+    /** @var string[] */
+    private $dataUrlRewriteClassNames;
 
     /**
      * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
-     * @param UrlPersistInterface $urlPersist
      * @param UrlRewriteHandler $urlRewriteHandler
+     * @param UrlRewriteBunchReplacer $urlRewriteBunchReplacer
+     * @param DatabaseMapPool $databaseMapPool
+     * @param string[] $dataUrlRewriteClassNames
      */
     public function __construct(
         CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
-        UrlPersistInterface $urlPersist,
-        UrlRewriteHandler $urlRewriteHandler
+        UrlRewriteHandler $urlRewriteHandler,
+        UrlRewriteBunchReplacer $urlRewriteBunchReplacer,
+        DatabaseMapPool $databaseMapPool,
+        $dataUrlRewriteClassNames = [
+        DataCategoryUrlRewriteDatabaseMap::class,
+        DataProductUrlRewriteDatabaseMap::class
+        ]
     ) {
         $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
-        $this->urlPersist = $urlPersist;
         $this->urlRewriteHandler = $urlRewriteHandler;
-    }
-
-    /**
-     * Retrieve Url Rewrite Replacer based on bunches
-     *
-     * @deprecated
-     * @return UrlRewriteBunchReplacer
-     */
-    private function getUrlRewriteBunchReplacer()
-    {
-        if (!$this->urlRewriteBunchReplacer) {
-            $this->urlRewriteBunchReplacer = ObjectManager::getInstance()->get(UrlRewriteBunchReplacer::class);
-        }
-
-        return $this->urlRewriteBunchReplacer;
+        $this->urlRewriteBunchReplacer = $urlRewriteBunchReplacer;
+        $this->databaseMapPool = $databaseMapPool;
+        $this->dataUrlRewriteClassNames = $dataUrlRewriteClassNames;
     }
 
     /**
@@ -67,7 +63,7 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
     public function execute(\Magento\Framework\Event\Observer $observer)
     {
         /** @var Category $category */
-        $category = $observer->getEvent()->getCategory();
+        $category = $observer->getEvent()->getData('category');
         if ($category->getParentId() == Category::TREE_ROOT_ID) {
             return;
         }
@@ -75,12 +71,28 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
             || $category->dataHasChangedFor('is_anchor')
             || $category->getIsChangedProductList()
         ) {
-            $urlRewrites = array_merge(
-                $this->categoryUrlRewriteGenerator->generate($category),
-                $this->urlRewriteHandler->generateProductUrlRewrites($category)
-            );
+            $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category);
+            $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult);
+
+            $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category);
+            $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult);
 
-            $this->getUrlRewriteBunchReplacer()->doBunchReplace($urlRewrites);
+            //frees memory for maps that are self-initialized in multiple classes that were called by the generators
+            $this->resetUrlRewritesDataMaps($category);
         }
     }
+
+    /**
+     * Resets used data maps to free up memory and temporary tables
+     *
+     * @param Category $category
+     * @return void
+     */
+    private function resetUrlRewritesDataMaps($category)
+    {
+        foreach ($this->dataUrlRewriteClassNames as $className) {
+            $this->databaseMapPool->resetMap($className, $category->getEntityId());
+        }
+
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
index ff3ca37f2e23ee7b7b3ab74f7e1f3b8a13e734eb..9d53150675518ced86b210d17367fdc3fec714a1 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
@@ -5,27 +5,18 @@
  */
 namespace Magento\CatalogUrlRewrite\Observer;
 
-use Magento\Catalog\Model\Category;
-use Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\Event\Observer as EventObserver;
-use Magento\UrlRewrite\Model\UrlPersistInterface;
-use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
-
 class UrlRewriteHandler
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider */
     protected $childrenCategoriesProvider;
 
-    /** @var CategoryUrlRewriteGenerator */
+    /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator */
     protected $categoryUrlRewriteGenerator;
 
-    /** @var ProductUrlRewriteGenerator */
+    /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator */
     protected $productUrlRewriteGenerator;
 
-    /** @var UrlPersistInterface */
+    /** @var \Magento\UrlRewrite\Model\UrlPersistInterface */
     protected $urlPersist;
 
     /** @var array */
@@ -34,44 +25,64 @@ class UrlRewriteHandler
     /** @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */
     protected $productCollectionFactory;
 
-    /**
-     * @var CategoryBasedProductRewriteGenerator
-     */
+    /** @var \Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator */
     private $categoryBasedProductRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
+    /** @var \Magento\Framework\Serialize\Serializer\Json */
+    private $serializer;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider
-     * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
-     * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator
-     * @param UrlPersistInterface $urlPersist
+     * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
+     * @param \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator
+     * @param \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist
      * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
+     * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
      */
     public function __construct(
         \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider,
-        CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
-        ProductUrlRewriteGenerator $productUrlRewriteGenerator,
-        UrlPersistInterface $urlPersist,
-        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+        \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
+        \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator,
+        \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist,
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
+        \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null,
+        \Magento\Framework\Serialize\Serializer\Json $serializer = null
     ) {
         $this->childrenCategoriesProvider = $childrenCategoriesProvider;
         $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
         $this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
         $this->urlPersist = $urlPersist;
         $this->productCollectionFactory = $productCollectionFactory;
+
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory =  \Magento\Framework\App\ObjectManager::getInstance()->get(
+                \Magento\UrlRewrite\Model\MergeDataProviderFactory::class
+            );
+        }
+
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
+
+        $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+            \Magento\Framework\Serialize\Serializer\Json::class
+        );
     }
 
     /**
      * Generate url rewrites for products assigned to category
      *
-     * @param Category $category
+     * @param \Magento\Catalog\Model\Category $category
      * @return array
      */
-    public function generateProductUrlRewrites(Category $category)
+    public function generateProductUrlRewrites(\Magento\Catalog\Model\Category $category)
     {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         $this->isSkippedProduct = [];
         $saveRewriteHistory = $category->getData('save_rewrites_history');
         $storeId = $category->getStoreId();
-        $productUrls = [];
         if ($category->getAffectedProductIds()) {
             $this->isSkippedProduct = $category->getAffectedProductIds();
             $collection = $this->productCollectionFactory->create()
@@ -84,38 +95,54 @@ class UrlRewriteHandler
             foreach ($collection as $product) {
                 $product->setStoreId($storeId);
                 $product->setData('save_rewrites_history', $saveRewriteHistory);
-                $productUrls = array_merge($productUrls, $this->productUrlRewriteGenerator->generate($product));
+                $mergeDataProvider->merge(
+                    $this->productUrlRewriteGenerator->generate($product, $category->getEntityId())
+                );
             }
         } else {
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryProductsUrlRewrites($category, $storeId, $saveRewriteHistory)
+            $mergeDataProvider->merge(
+                $this->getCategoryProductsUrlRewrites(
+                    $category,
+                    $storeId,
+                    $saveRewriteHistory,
+                    $category->getEntityId()
+                )
             );
         }
         foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryProductsUrlRewrites($childCategory, $storeId, $saveRewriteHistory)
+            $mergeDataProvider->merge(
+                $this->getCategoryProductsUrlRewrites(
+                    $childCategory,
+                    $storeId,
+                    $saveRewriteHistory,
+                    $category->getEntityId()
+                )
             );
         }
-        return $productUrls;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
-     * @param Category $category
+     * @param \Magento\Catalog\Model\Category $category
      * @param int $storeId
      * @param bool $saveRewriteHistory
-     * @return UrlRewrite[]
+     * @param int|null $rootCategoryId
+     * @return array
      */
-    public function getCategoryProductsUrlRewrites(Category $category, $storeId, $saveRewriteHistory)
-    {
+    public function getCategoryProductsUrlRewrites(
+        \Magento\Catalog\Model\Category $category,
+        $storeId,
+        $saveRewriteHistory,
+        $rootCategoryId = null
+    ) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */
         $productCollection = $category->getProductCollection()
             ->addAttributeToSelect('name')
             ->addAttributeToSelect('visibility')
             ->addAttributeToSelect('url_key')
             ->addAttributeToSelect('url_path');
-        $productUrls = [];
         foreach ($productCollection as $product) {
             if (in_array($product->getId(), $this->isSkippedProduct)) {
                 continue;
@@ -123,49 +150,53 @@ class UrlRewriteHandler
             $this->isSkippedProduct[] = $product->getId();
             $product->setStoreId($storeId);
             $product->setData('save_rewrites_history', $saveRewriteHistory);
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category)
+            $mergeDataProvider->merge(
+                $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category, $rootCategoryId)
             );
         }
-        return $productUrls;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
      * Retrieve generator, which use single category for different products
      *
      * @deprecated
-     * @return CategoryBasedProductRewriteGenerator|mixed
+     * @return \Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator|mixed
      */
     private function getCategoryBasedProductRewriteGenerator()
     {
         if (!$this->categoryBasedProductRewriteGenerator) {
-            $this->categoryBasedProductRewriteGenerator = ObjectManager::getInstance()
-                ->get(CategoryBasedProductRewriteGenerator::class);
+            $this->categoryBasedProductRewriteGenerator = \Magento\Framework\App\ObjectManager::getInstance()
+                ->get(\Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator::class);
         }
 
         return $this->categoryBasedProductRewriteGenerator;
     }
 
     /**
-     * @param Category $category
+     * @param \Magento\Catalog\Model\Category $category
      * @return void
      */
-    public function deleteCategoryRewritesForChildren(Category $category)
+    public function deleteCategoryRewritesForChildren(\Magento\Catalog\Model\Category $category)
     {
         $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true);
         $categoryIds[] = $category->getId();
         foreach ($categoryIds as $categoryId) {
             $this->urlPersist->deleteByData(
                 [
-                    UrlRewrite::ENTITY_ID => $categoryId,
-                    UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
+                    \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_ID =>
+                        $categoryId,
+                    \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE =>
+                        \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::ENTITY_TYPE,
                 ]
             );
             $this->urlPersist->deleteByData(
                 [
-                    UrlRewrite::METADATA => serialize(['category_id' => $categoryId]),
-                    UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+                    \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA =>
+                        $this->serializer->serialize(['category_id' => $categoryId]),
+                    \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE =>
+                        \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE,
                 ]
             );
         }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
index 2766bf630739e4fe0562249ee82253fe661786f6..6ff97830c8e8fb95f740143b08ad521dd14322f4 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
@@ -6,26 +6,36 @@
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category;
 
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
 
 class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */
-    protected $childrenUrlRewriteGenerator;
+    private $childrenUrlRewriteGenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $childrenCategoriesProvider;
+    private $childrenCategoriesProvider;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlRewriteGeneratorFactory;
+    private $categoryUrlRewriteGeneratorFactory;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $serializerMock;
 
     protected function setUp()
     {
+        $this->serializerMock = $this->getMockBuilder(Json::class)
+            ->disableOriginalConstructor()
+            ->getMock();
         $this->childrenCategoriesProvider = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider::class
         )->disableOriginalConstructor()->getMock();
@@ -37,18 +47,29 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->categoryUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
+
         $this->childrenUrlRewriteGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class,
             [
                 'childrenCategoriesProvider' => $this->childrenCategoriesProvider,
-                'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory
+                'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
 
     public function testNoChildrenCategories()
     {
-        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false)
+        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true)
             ->will($this->returnValue([]));
 
         $this->assertEquals([], $this->childrenUrlRewriteGenerator->generate('store_id', $this->category));
@@ -64,18 +85,28 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $childCategory->expects($this->once())->method('setStoreId')->with($storeId);
         $childCategory->expects($this->once())->method('setData')
             ->with('save_rewrites_history', $saveRewritesHistory);
-        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false)
+        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true)
             ->will($this->returnValue([$childCategory]));
         $this->category->expects($this->any())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue($saveRewritesHistory));
         $this->categoryUrlRewriteGeneratorFactory->expects($this->once())->method('create')
             ->will($this->returnValue($this->categoryUrlRewriteGenerator));
-        $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')->with($childCategory)
-            ->will($this->returnValue([['url-1', 'url-2']]));
+        $url1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock);
+        $url1->setRequestPath('category-1')
+            ->setStoreId(1);
+        $url2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock);
+        $url2->setRequestPath('category-2')
+            ->setStoreId(2);
+        $url3 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock);
+        $url3->setRequestPath('category-1')
+            ->setStoreId(1);
+        $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')
+            ->with($childCategory, false, 1)
+            ->will($this->returnValue([$url1, $url2, $url3]));
 
         $this->assertEquals(
-            [['url-1', 'url-2']],
-            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category)
+            ['category-1_1'  => $url1, 'category-2_2' => $url2],
+            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category, 1)
         );
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
index 2f9a9463646f1f54382720c649e22565eec62683..483df828fb0446a046ca649de83f0511027d531c 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
@@ -14,25 +14,25 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
 class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator */
-    protected $currentUrlRewritesRegenerator;
-
-    /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $filter;
-
-    /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlFinder;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlPathGenerator;
+    private $categoryUrlPathGenerator;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewrite;
+    private $urlRewrite;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFinder;
 
     protected function setUp()
     {
@@ -43,41 +43,50 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()->getMock();
         $this->category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class)
             ->disableOriginalConstructor()->getMock();
-        $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf());
-        $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf());
-        $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->categoryUrlPathGenerator = $this->getMockBuilder(
+        $this->categoryUrlPathGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(
+        $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class)
+            ->disableOriginalConstructor()->getMock();
+        $this->urlRewriteFactory->expects($this->once())->method('create')
+            ->willReturn($this->urlRewrite);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
+
+        $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(   
             \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class,
             [
-                'urlFinder' => $this->urlFinder,
                 'categoryUrlPathGenerator' => $this->categoryUrlPathGenerator,
-                'urlRewriteFactory' => $this->urlRewriteFactory
+                'urlRewriteFactory' => $this->urlRewriteFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory,
+                'urlRewriteFinder' => $this->urlRewriteFinder
             ]
         );
     }
 
     public function testIsAutogeneratedWithoutSaveRewriteHistory()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]])));
         $this->category->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(false));
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
     public function testSkipGenerationForAutogenerated()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -94,7 +103,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
@@ -104,7 +113,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $targetPath = 'some-path.html';
         $storeId = 2;
         $categoryId = 12;
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -120,23 +129,24 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
                     )
                 )
             );
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->category->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
 
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT);
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT, 0);
 
         $this->assertEquals(
             ['autogenerated.html_2' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
     public function testSkipGenerationForCustom()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -155,7 +165,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
@@ -166,7 +176,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'custom-target-path.html';
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -184,16 +194,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
                 )
             );
         $this->categoryUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix');
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->urlRewrite->expects($this->once())->method('setDescription')->with($description)
             ->will($this->returnSelf());
-        $this->urlRewriteFactory->expects($this->once())->method('create')
-            ->willReturn($this->urlRewrite);
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0);
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0, 0);
 
         $this->assertEquals(
             ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
@@ -204,7 +212,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'generated-target-path.html';
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -223,16 +231,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             );
         $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->urlRewrite->expects($this->once())->method('setDescription')->with($description)
             ->will($this->returnSelf());
-        $this->urlRewriteFactory->expects($this->once())->method('create')
-            ->willReturn($this->urlRewrite);
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code');
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code', 0);
 
         $this->assertEquals(
             ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
@@ -263,9 +269,16 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
      * @param mixed $requestPath
      * @param mixed $targetPath
      * @param mixed $redirectType
+     * @param int $isAutoGenerated
      */
-    protected function prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, $redirectType)
-    {
+    protected function prepareUrlRewriteMock(
+        $storeId,
+        $categoryId,
+        $requestPath,
+        $targetPath,
+        $redirectType,
+        $isAutoGenerated
+    ) {
         $this->urlRewrite->expects($this->any())->method('setStoreId')->with($storeId)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setEntityId')->with($categoryId)
@@ -276,11 +289,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath)
             ->will($this->returnSelf());
-        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0)
+        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($isAutoGenerated)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setMetadata')->with([])->will($this->returnSelf());
+        $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn($targetPath);
+        $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn($requestPath);
+        $this->urlRewrite->expects($this->any())->method('getStoreId')->willReturn($storeId);
         $this->urlRewriteFactory->expects($this->any())->method('create')->will($this->returnValue($this->urlRewrite));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php
index 75b0509e0245db845bf3c82f6e6502affbcdb898..a2cd880e85acca6c60df9078b54df53f28d5c3ca 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php
@@ -39,6 +39,9 @@ class RemoveTest extends \PHPUnit_Framework_TestCase
      */
     private $objectMock;
 
+    /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */
+    private $serializerMock;
+
     protected function setUp()
     {
         $this->objectManager = new ObjectManager($this);
@@ -52,6 +55,7 @@ class RemoveTest extends \PHPUnit_Framework_TestCase
         $this->objectMock = $this->getMockBuilder(Category::class)
             ->disableOriginalConstructor()
             ->getMock();
+        $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false);
     }
 
     public function testAroundDelete()
@@ -64,7 +68,8 @@ class RemoveTest extends \PHPUnit_Framework_TestCase
             CategoryRemovePlugin::class,
             [
                 'urlPersist' => $this->urlPersistMock,
-                'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock
+                'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock,
+                'serializer' => $this->serializerMock
             ]
         );
         $this->childrenCategoriesProviderMock->expects($this->once())
@@ -76,6 +81,9 @@ class RemoveTest extends \PHPUnit_Framework_TestCase
             ->willReturn(1);
         $this->urlPersistMock->expects($this->exactly(2))
             ->method('deleteByData');
+        $this->serializerMock->expects($this->once())
+            ->method('serialize')
+            ->with(['category_id' => 1]);
         $this->assertSame(
             $this->subjectMock,
             $plugin->aroundDelete($this->subjectMock, $proceed, $this->objectMock)
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
index aad5e9ffec89811e2f21fca5716a9d9f0e2f0c40..927c451e6522865454220c4401ddbcc21a3f4b8f 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
@@ -6,7 +6,6 @@
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category\Plugin;
 
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\CatalogUrlRewrite\Model\Category\ProductFactory;
 use Magento\UrlRewrite\Model\StorageInterface;
 use Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage as CategoryStoragePlugin;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
@@ -24,11 +23,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase
      */
     private $plugin;
 
-    /**
-     * @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $productFactory;
-
     /**
      * @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject
      */
@@ -56,10 +50,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->productFactory = $this->getMockBuilder(ProductFactory::class)
-            ->disableOriginalConstructor()
-            ->setMethods(['create'])
-            ->getMock();
         $this->storage = $this->getMockBuilder(StorageInterface::class)
             ->getMockForAbstractClass();
         $this->urlFinder = $this->getMockBuilder(UrlFinderInterface::class)
@@ -78,12 +68,15 @@ class StorageTest extends \PHPUnit_Framework_TestCase
         $this->plugin = (new ObjectManager($this))->getObject(
             CategoryStoragePlugin::class,
             [
-                'productFactory' => $this->productFactory,
-                'urlFinder' => $this->urlFinder
+                'urlFinder' => $this->urlFinder,
+                'productResource' => $this->productResourceModel
             ]
         );
     }
 
+    /**
+     * test AfterReplace method
+     */
     public function testAfterReplace()
     {
         $this->urlRewrite->expects(static::any())->method('getMetadata')->willReturn(['category_id' => '5']);
@@ -97,10 +90,20 @@ class StorageTest extends \PHPUnit_Framework_TestCase
 
         $this->urlFinder->expects(static::once())->method('findAllByData')->willReturn([$this->urlRewrite]);
 
-        $this->productFactory->expects(static::once())->method('create')->willReturn($this->product);
-        $this->product->expects(static::once())->method('getResource')->willReturn($this->productResourceModel);
         $this->productResourceModel->expects(static::once())->method('saveMultiple')->willReturnSelf();
 
         $this->plugin->afterReplace($this->storage, null, $productUrls);
     }
+
+    /**
+     * test BeforeDeleteByData method
+     */
+    public function testBeforeDeleteByData()
+    {
+        $data = [1, 2, 3];
+        $this->productResourceModel->expects(static::once())
+            ->method('removeMultipleByProductCategory')
+            ->with($data)->willReturnSelf();
+        $this->plugin->beforeDeleteByData($this->storage, $data);
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
index 035112a592916060e637ad284ea4845ebbfb5d66..32f310c33b8cbcf27cda92c279e14f39e39394e2 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
@@ -12,12 +12,11 @@ use Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator;
 
 /**
  * Class CategoryBasedProductRewriteGeneratorTest
- * @package Magento\CatalogUrlRewrite\Test\Unit\Model
  */
 class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var ProductScopeRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
     private $productScopeRewriteGeneratorMock;
 
@@ -46,6 +45,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->disableOriginalConstructor()
             ->getMock();
         $storeId = 1;
+        $categoryId = 1;
         $urls = ['dummy-url.html'];
 
         $productMock->expects($this->once())
@@ -60,10 +60,10 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->willReturn(true);
         $this->productScopeRewriteGeneratorMock->expects($this->once())
             ->method('generateForGlobalScope')
-            ->with([$categoryMock], $productMock)
+            ->with([$categoryMock], $productMock, $categoryId)
             ->willReturn($urls);
 
-        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock));
+        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId));
     }
 
     public function testGenerationWithSpecificStore()
@@ -75,6 +75,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->disableOriginalConstructor()
             ->getMock();
         $storeId = 1;
+        $categoryId = 1;
         $urls = ['dummy-url.html'];
 
         $productMock->expects($this->once())
@@ -89,9 +90,9 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->willReturn(false);
         $this->productScopeRewriteGeneratorMock->expects($this->once())
             ->method('generateForSpecificStoreView')
-            ->with($storeId, [$categoryMock], $productMock)
+            ->with($storeId, [$categoryMock], $productMock, $categoryId)
             ->willReturn($urls);
 
-        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock));
+        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
index d5abe69ff79777cf02067899eff0d434c76620a9..f36799f789fbdb3a9c287f4e14e1c3abab5b3c36 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
@@ -5,8 +5,7 @@
  */
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model;
 
-use \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
-
+use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
 use Magento\Catalog\Model\Category;
 use Magento\Store\Model\ScopeInterface;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
index c2905eb9b425f243126547bda3791bf570ee6977..cfc6243525f42240136394befa6b3c5dfe4ea49f 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
@@ -8,52 +8,87 @@
 
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model;
 
-use Magento\Catalog\Model\Category;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 
+/**
+ * Class CategoryUrlRewriteGeneratorTest
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
 class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $canonicalUrlRewriteGenerator;
+    private $canonicalUrlRewriteGenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $currentUrlRewritesRegenerator;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $childrenUrlRewriteGenerator;
+    private $childrenUrlRewriteGenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject */
-    protected $storeViewService;
+    private $storeViewService;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryRepository;
+    private $categoryRepository;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */
+    protected $serializer;
 
     /**
      * Test method
      */
     protected function setUp()
     {
-        $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
+        $this->serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false);
+        $this->serializer->expects($this->any())
+            ->method('serialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_encode($value);
+                }
+            );
+        $this->serializer->expects($this->any())
+            ->method('unserialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_decode($value, true);
+                }
+            );
+        
+        $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->canonicalUrlRewriteGenerator = $this->getMockBuilder(
+        $this->canonicalUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->childrenUrlRewriteGenerator = $this->getMockBuilder(
+        $this->childrenUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
         $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class)
             ->disableOriginalConstructor()->getMock();
         $this->category = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false);
         $this->categoryRepository = $this->getMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
-        $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject(
+        $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class,
             [
                 'canonicalUrlRewriteGenerator' => $this->canonicalUrlRewriteGenerator,
@@ -61,6 +96,7 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
                 'currentUrlRewritesRegenerator' => $this->currentUrlRewritesRegenerator,
                 'storeViewService' => $this->storeViewService,
                 'categoryRepository' => $this->categoryRepository,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -70,26 +106,32 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
      */
     public function testGenerationForGlobalScope()
     {
+        $categoryId = 1;
         $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(null));
         $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1]));
         $this->storeViewService->expects($this->once())->method('doesEntityHaveOverriddenUrlKeyForStore')
             ->will($this->returnValue(false));
-        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$canonical]));
-        $children = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $children->setTargetPath('category-2')
+            ->will($this->returnValue(['category-1' => $canonical]));
+        $children1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $children1->setRequestPath('category-2')
+            ->setStoreId(2);
+        $children2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $children2->setRequestPath('category-22')
             ->setStoreId(2);
         $this->childrenUrlRewriteGenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$children]));
-        $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $current->setTargetPath('category-3')
+            ->with(1, $this->category, $categoryId)
+            ->will($this->returnValue(['category-2' => $children1, 'category-1' => $children2]));
+        $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $current->setRequestPath('category-3')
             ->setStoreId(3);
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$current]));
-        $categoryForSpecificStore = $this->getMock(
+            ->with(1, $this->category, $categoryId)
+            ->will($this->returnValue(['category-3' => $current]));
+        $categoryForSpecificStore = $this->getMock(
             \Magento\Catalog\Model\Category::class,
             ['getUrlKey', 'getUrlPath'],
             [],
@@ -99,8 +141,13 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->categoryRepository->expects($this->once())->method('get')->willReturn($categoryForSpecificStore);
 
         $this->assertEquals(
-            [$canonical, $children, $current],
-            $this->categoryUrlRewriteGenerator->generate($this->category)
+            [
+                'category-1_1' => $canonical,
+                'category-2_2' => $children1,
+                'category-22_2' => $children2,
+                'category-3_3' => $current
+            ],
+            $this->categoryUrlRewriteGenerator->generate($this->category, false, $categoryId)
         );
     }
 
@@ -111,8 +158,8 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
     {
         $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(1));
         $this->category->expects($this->never())->method('getStoreIds');
-        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
@@ -121,7 +168,10 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([]));
 
-        $this->assertEquals([$canonical], $this->categoryUrlRewriteGenerator->generate($this->category));
+        $this->assertEquals(
+            ['category-1_1' => $canonical],
+            $this->categoryUrlRewriteGenerator->generate($this->category, 1)
+        );
     }
 
     /**
@@ -135,4 +185,18 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category));
     }
+
+    /**
+     * Test method
+     */
+    public function testSkipGenerationForGlobalScopeWithCategory()
+    {
+        $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1, 2]));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue(1));
+        $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(false));
+        $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore')
+            ->will($this->returnValue(true));
+
+        $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category, false, 1));
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..52d65710fed69a0902f28aacb25a5183fa28e833
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\CategoryFactory;
+use Magento\Catalog\Model\ResourceModel\Category;
+use Magento\Framework\DB\Select;
+use Magento\Catalog\Model\CategoryRepository;
+use Magento\Catalog\Api\Data\CategoryInterface;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+
+/**
+ * Class DataCategoryHashMapTest
+ */
+class DataCategoryHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var CategoryRepository|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryRepository;
+
+    /** @var CategoryResourceFactory|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryResourceFactory;
+
+    /** @var Category|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryResource;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->categoryRepository = $this->getMock(CategoryRepository::class, [], [], '', false);
+        $this->categoryResourceFactory = $this->getMock(CategoryFactory::class, ['create'], [], '', false);
+        $this->categoryResource = $this->getMock(
+            Category::class,
+            ['getConnection', 'getEntityTable'],
+            [],
+            '',
+            false
+        );
+
+        $this->categoryResourceFactory->expects($this->any())
+            ->method('create')
+            ->willReturn($this->categoryResource);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryHashMap::class,
+            [
+                'categoryRepository' => $this->categoryRepository,
+                'categoryResourceFactory' => $this->categoryResourceFactory
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $categoryIdsOther = ['2' => [2, 3, 4]];
+
+        $categoryMock = $this->getMock(CategoryInterface::class, [], [], '', false);
+        $connectionAdapterMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->categoryRepository->expects($this->any())
+            ->method('get')
+            ->willReturn($categoryMock);
+        $categoryMock->expects($this->any())
+            ->method('getResource')
+            ->willReturn($this->categoryResource);
+        $this->categoryResource->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionAdapterMock);
+        $this->categoryResource->expects($this->any())
+            ->method('getEntityTable')
+            ->willReturn('category_entity');
+        $connectionAdapterMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $connectionAdapterMock->expects($this->any())
+            ->method('fetchCol')
+            ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds);
+
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7891c56be1368b7917bfb01e33a8a84ef79eba6b
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+
+/**
+ * Class DataCategoryUrlRewriteDatabaseMapTest
+ */
+class DataCategoryUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryUsedInProductsMapMock;
+
+    /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */
+    private $temporaryTableServiceMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataCategoryUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->dataCategoryUsedInProductsMapMock = $this->getMock(
+            DataCategoryUsedInProductsHashMap::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturnOnConsecutiveCalls($this->dataCategoryUsedInProductsMapMock, $this->dataCategoryMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryUrlRewriteDatabaseMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock,
+                'temporaryTableService' => $this->temporaryTableServiceMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productStoreIds = [
+            '1' => ['store_id' => 1, 'category_id' => 1],
+            '2' => ['store_id' => 2, 'category_id' => 1],
+            '3' => ['store_id' => 3, 'category_id' => 1],
+            '4' => ['store_id' => 1, 'category_id' => 2],
+            '5' => ['store_id' => 2, 'category_id' => 2],
+        ];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchAll')
+            ->willReturn($productStoreIds[3]);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->dataCategoryMapMock->expects($this->once())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->dataCategoryUsedInProductsMapMock->expects($this->once())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->temporaryTableServiceMock->expects($this->any())
+            ->method('createFromSelect')
+            ->withConsecutive(
+                $selectMock,
+                $connectionMock,
+                [
+                    'PRIMARY' => ['url_rewrite_id'],
+                    'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                    'ENTITY_STORE' => ['entity_id', 'store_id']
+                ]
+            )
+            ->willReturn('tempTableName');
+
+        $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1'));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..74353d3a924f7687eb238399d38380bffcab36c5
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Class DataCategoryUsedInProductsHashMapTest
+ */
+class DataCategoryUsedInProductsHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataProductMapMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturnOnConsecutiveCalls(
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock,
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock,
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock
+            );
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryUsedInProductsHashMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $categoryIdsOther = ['2' => [2, 3, 4]];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchCol')
+            ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->hashMapPoolMock->expects($this->at(4))
+            ->method('resetMap')
+            ->with(DataProductHashMap::class, 1);
+        $this->hashMapPoolMock->expects($this->at(5))
+            ->method('resetMap')
+            ->with(DataCategoryHashMap::class, 1);
+
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..53e4d225977698c0a3b3bd4afe7809a58f3623bd
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\Category\Collection;
+use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
+use Magento\Framework\DB\Select;
+use Magento\Catalog\Model\ProductRepository;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
+
+/**
+ * Class DataProductHashMapTest
+ */
+class DataProductHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /**
+     * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $collectionFactoryMock;
+
+    /**
+     * @var ProductCollection|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productCollectionMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $this->collectionFactoryMock = $this->getMock(CollectionFactory::class, ['create'], [], '', false);
+        $this->productCollectionMock = $this->getMock(
+            ProductCollection::class,
+            ['getSelect', 'getConnection', 'getAllIds'],
+            [],
+            '',
+            false
+        );
+
+        $this->collectionFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($this->productCollectionMock);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturn($this->dataCategoryMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataProductHashMap::class,
+            [
+                'collectionFactory' => $this->collectionFactoryMock,
+                'hashMapPool' => $this->hashMapPoolMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $productIdsOther = ['2' => [2, 3, 4]];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->productCollectionMock->expects($this->exactly(3))
+            ->method('getAllIds')
+            ->willReturnOnConsecutiveCalls($productIds, $productIdsOther, $productIds);
+        $this->productCollectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('getTableName')
+            ->willReturn($this->returnValue($this->returnArgument(0)));
+        $this->productCollectionMock->expects($this->any())
+            ->method('getSelect')
+            ->willReturn($selectMock);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->dataCategoryMapMock->expects($this->any())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('resetMap')
+            ->with(DataCategoryHashMap::class, 1);
+        $this->assertEquals($productIds, $this->model->getAllData(1));
+        $this->assertEquals($productIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($productIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($productIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($productIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($productIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d283c6d3f0410f9df87036be147f47c17ca7f212
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+
+/**
+ * Class DataProductUrlRewriteDatabaseMapTest
+ */
+class DataProductUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataProductMapMock;
+
+    /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */
+    private $temporaryTableServiceMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataProductUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturn($this->dataProductMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataProductUrlRewriteDatabaseMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock,
+                'temporaryTableService' => $this->temporaryTableServiceMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productStoreIds = [
+            '1' => ['store_id' => 1, 'product_id' => 1],
+            '2' => ['store_id' => 2, 'product_id' => 1],
+            '3' => ['store_id' => 3, 'product_id' => 1],
+            '4' => ['store_id' => 1, 'product_id' => 2],
+            '5' => ['store_id' => 2, 'product_id' => 2],
+        ];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchAll')
+            ->willReturn($productStoreIds[3]);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+
+        $this->dataProductMapMock->expects($this->any())
+            ->method('getAllData')
+            ->willReturn([]);
+
+        $this->temporaryTableServiceMock->expects($this->any())
+            ->method('createFromSelect')
+            ->withConsecutive(
+                $selectMock,
+                $connectionMock,
+                [
+                    'PRIMARY' => ['url_rewrite_id'],
+                    'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                    'ENTITY_STORE' => ['entity_id', 'store_id']
+                ]
+            )
+            ->willReturn('tempTableName');
+
+        $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1'));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9efdb01f2e1dba3d8b0cc3385181fa382baa52e
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Class HashMapPoolTest
+ */
+class HashMapPoolTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $objectManagerMock;
+
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            HashMapPool::class,
+            [
+                'objectManager' => $this->objectManagerMock,
+            ]
+        );
+    }
+
+    /**
+     * Tests getDataMap
+     */
+    public function testGetDataMap()
+    {
+        $dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $dataProductMapMockOtherCategory = $this->getMock(DataCategoryUsedInProductsHashMap::class, [], [], '', false);
+
+        $this->objectManagerMock->expects($this->any())
+            ->method('create')
+            ->willReturnMap(
+                [
+                    [
+                        DataCategoryHashMap::class,
+                        ['category' => 1],
+                        $dataCategoryMapMock
+                    ],
+                    [
+                        DataProductHashMap::class,
+                        ['category' => 1],
+                        $dataProductMapMock
+                    ],
+                    [
+                        DataCategoryUsedInProductsHashMap::class,
+                        ['category' => 2],
+                        $dataProductMapMockOtherCategory
+                    ]
+               ]
+            );
+        $this->assertSame($dataCategoryMapMock, $this->model->getDataMap(DataCategoryHashMap::class, 1));
+        $this->assertSame($dataProductMapMock, $this->model->getDataMap(DataProductHashMap::class, 1));
+        $this->assertSame(
+            $dataProductMapMockOtherCategory,
+            $this->model->getDataMap(DataCategoryUsedInProductsHashMap::class, 2)
+        );
+    }
+
+    /**
+     * Tests getDataMap with exception
+     */
+    public function testGetDataMapException()
+    {
+        $nonInterface = $this->getMock(HashMapPool::class, [], [], '', false);
+
+        $this->objectManagerMock->expects($this->any())
+            ->method('create')
+            ->willReturn($nonInterface);
+        $this->setExpectedException(\InvalidArgumentException::class);
+        $this->model->getDataMap(HashMapPool::class, 1);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca5581f366352a6021d01338e756451db1a34d50
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder;
+use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool;
+use Magento\UrlRewrite\Model\UrlFinderInterface;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
+use Magento\Framework\Serialize\Serializer\Json;
+
+/**
+ * Class UrlRewriteFinderTest
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class UrlRewriteFinderTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var DatabaseMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $databaseMapPoolMock;
+
+    /** @var UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFactoryMock;
+
+    /** @var UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewritePrototypeMock;
+
+    /** @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlFinderMock;
+
+    /** @var UrlRewriteFinder|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $serializerMock;
+
+    protected function setUp()
+    {
+        $this->serializerMock = $this->getMock(Json::class, [], [], '', false);
+        $this->databaseMapPoolMock = $this->getMock(DatabaseMapPool::class, [], [], '', false);
+        $this->urlFinderMock = $this->getMock(UrlFinderInterface::class);
+        $this->urlRewriteFactoryMock = $this->getMock(UrlRewriteFactory::class, ['create'], [], '', false);
+        $this->urlRewritePrototypeMock = new UrlRewrite([], $this->serializerMock);
+
+        $this->urlRewriteFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($this->urlRewritePrototypeMock);
+
+        $urlRewriteClassesNamesArray = [
+            UrlRewriteFinder::ENTITY_TYPE_PRODUCT => DataProductUrlRewriteDatabaseMap::class,
+            UrlRewriteFinder::ENTITY_TYPE_CATEGORY => DataCategoryUrlRewriteDatabaseMap::class
+        ];
+
+        $this->model = (new ObjectManager($this))->getObject(
+            UrlRewriteFinder::class,
+            [
+                'databaseMapPool' => $this->databaseMapPoolMock,
+                'urlFinder' => $this->urlFinderMock,
+                'urlRewriteFactory' => $this->urlRewriteFactoryMock,
+                'urlRewriteClassNames' => $urlRewriteClassesNamesArray
+            ]
+        );
+    }
+
+    /**
+     * test findAllByData using urlFinder
+     */
+    public function testGetByIdentifiersFallback()
+    {
+        $expected = [1, 2, 3];
+        $this->databaseMapPoolMock->expects($this->never())
+            ->method('getDataMap');
+
+        $this->urlFinderMock->expects($this->exactly(7))
+            ->method('findAllByData')
+            ->willReturn($expected);
+
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY));
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT));
+        $this->assertEquals($expected, $this->model->findAllByData('a', 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData('a', 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData(1, 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms', 1));
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms'));
+    }
+
+    /**
+     * test findAllByData Product URL rewrites
+     */
+    public function testGetByIdentifiersProduct()
+    {
+        $data =[
+            [
+                'url_rewrite_id' => '1',
+                'entity_type' => 'product',
+                'entity_id' => '3',
+                'request_path' => 'request_path',
+                'target_path' => 'target_path',
+                'redirect_type' => 'redirect_type',
+                'store_id' => '4',
+                'description' => 'description',
+                'is_autogenerated' => '1',
+                'metadata' => '{}'
+            ]
+        ];
+
+        $dataProductMapMock = $this->getMock(DataProductUrlRewriteDatabaseMap::class, [], [], '', false);
+        $this->databaseMapPoolMock->expects($this->once())
+            ->method('getDataMap')
+            ->with(DataProductUrlRewriteDatabaseMap::class, 1)
+            ->willReturn($dataProductMapMock);
+
+        $this->urlFinderMock->expects($this->never())
+            ->method('findAllByData')
+            ->willReturn([]);
+
+        $dataProductMapMock->expects($this->once())
+            ->method('getData')
+            ->willReturn($data);
+
+        $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT, 1);
+        $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray());
+    }
+
+    /**
+     * test findAllByData Category URL rewrites
+     */
+    public function testGetByIdentifiersCategory()
+    {
+        $data =[
+            [
+                'url_rewrite_id' => '1',
+                'entity_type' => 'category',
+                'entity_id' => '3',
+                'request_path' => 'request_path',
+                'target_path' => 'target_path',
+                'redirect_type' => 'redirect_type',
+                'store_id' => '4',
+                'description' => 'description',
+                'is_autogenerated' => '1',
+                'metadata' => '{}'
+            ]
+        ];
+
+        $dataCategoryMapMock = $this->getMock(DataCategoryUrlRewriteDatabaseMap::class, [], [], '', false);
+        $this->databaseMapPoolMock->expects($this->once())
+            ->method('getDataMap')
+            ->with(DataCategoryUrlRewriteDatabaseMap::class, 1)
+            ->willReturn($dataCategoryMapMock);
+
+        $this->urlFinderMock->expects($this->never())
+            ->method('findAllByData')
+            ->willReturn([]);
+
+        $dataCategoryMapMock->expects($this->once())
+            ->method('getData')
+            ->willReturn($data);
+
+        $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY, 1);
+        $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray());
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
index 28aee3c7c39f463a6740a3cc32cf227207a0d827..b5515c2c254593bd5d7f015584b9b7e62f182bd3 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
@@ -16,31 +16,31 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
 class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator */
-    protected $currentUrlRewritesRegenerator;
-
-    /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $filter;
-
-    /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlFinder;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */
-    protected $productUrlPathGenerator;
+    private $productUrlPathGenerator;
 
     /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */
-    protected $product;
+    private $product;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */
-    protected $objectRegistry;
+    private $objectRegistry;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewrite;
+    private $urlRewrite;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFinder;
 
     protected function setUp()
     {
@@ -55,28 +55,36 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()->getMock();
         $this->objectRegistry = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\ObjectRegistry::class)
             ->disableOriginalConstructor()->getMock();
-        $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf());
-        $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf());
-        $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class)
+        $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class)
             ->disableOriginalConstructor()->getMock();
+        $this->urlRewriteFactory->expects($this->once())->method('create')
+            ->willReturn($this->urlRewrite);
         $this->productUrlPathGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class
         )->disableOriginalConstructor()->getMock();
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
         $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator::class,
             [
-                'urlFinder' => $this->urlFinder,
                 'productUrlPathGenerator' => $this->productUrlPathGenerator,
-                'urlRewriteFactory' => $this->urlRewriteFactory
+                'urlRewriteFactory' => $this->urlRewriteFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory,
+                'urlRewriteFinder' => $this->urlRewriteFinder
             ]
         );
     }
 
     public function testIsAutogeneratedWithoutSaveRewriteHistory()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]])));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(false));
@@ -89,7 +97,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
     public function testSkipGenerationForAutogenerated()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [UrlRewrite::IS_AUTOGENERATED => 1, UrlRewrite::REQUEST_PATH => 'same-path'],
             ])));
@@ -100,7 +108,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry, 1)
         );
     }
 
@@ -108,21 +116,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
     {
         $requestPath = 'autogenerated.html';
         $targetPath = 'some-path.html';
+        $autoGenerated = 1;
         $storeId = 2;
         $productId = 12;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'custom-target-path',
                     UrlRewrite::STORE_ID => $storeId,
-                    UrlRewrite::IS_AUTOGENERATED => 1,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::METADATA => [],
                     UrlRewrite::DESCRIPTION => $description,
                 ],
             ])));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
@@ -133,6 +142,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             $productId,
             $requestPath,
             $targetPath,
+            0,
             OptionProvider::PERMANENT,
             [],
             $description
@@ -140,7 +150,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [$this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 1)
         );
     }
 
@@ -149,21 +159,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 12;
         $requestPath = 'autogenerated.html';
         $targetPath = 'simple-product.html';
+        $autoGenerated = 1;
         $storeId = 2;
         $metadata = ['category_id' => 2, 'some_another_data' => 1];
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'some-path.html',
                     UrlRewrite::STORE_ID => $storeId,
-                    UrlRewrite::IS_AUTOGENERATED => 1,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::METADATA => $metadata,
                     UrlRewrite::DESCRIPTION => $description,
                 ],
             ])));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
@@ -174,6 +185,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             $productId,
             $requestPath,
             $targetPath,
+            0,
             OptionProvider::PERMANENT,
             $metadata,
             $description
@@ -181,13 +193,13 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [$this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 2)
         );
     }
 
     public function testSkipGenerationForCustom()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::IS_AUTOGENERATED => 0,
@@ -210,21 +222,31 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 123;
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'custom-target-path.html';
+        $autoGenerated = 0;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => $targetPath,
                     UrlRewrite::REDIRECT_TYPE => 0,
-                    UrlRewrite::IS_AUTOGENERATED => 0,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::DESCRIPTION => $description,
                     UrlRewrite::METADATA => [],
                 ],
             ])));
         $this->productUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix');
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
-        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, [], $description);
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
+        $this->prepareUrlRewriteMock(
+            $storeId,
+            $productId,
+            $requestPath,
+            $targetPath,
+            $autoGenerated,
+            0,
+            [],
+            $description
+        );
 
         $this->assertEquals(
             [$this->urlRewrite],
@@ -238,22 +260,23 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 123;
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'generated-target-path.html';
+        $autoGenerated = 0;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'custom-target-path.html',
                     UrlRewrite::REDIRECT_TYPE => 'code',
-                    UrlRewrite::IS_AUTOGENERATED => 0,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::DESCRIPTION => $description,
                     UrlRewrite::METADATA => [],
                 ],
             ])));
         $this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
-        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 'code', [], $description);
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
+        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, 'code', [], $description);
 
         $this->assertEquals(
             [$this->urlRewrite],
@@ -287,6 +310,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
      * @param mixed $productId
      * @param mixed $requestPath
      * @param mixed $targetPath
+     * @param mixed $autoGenerated
      * @param mixed $redirectType
      * @param mixed $metadata
      * @param mixed $description
@@ -296,6 +320,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId,
         $requestPath,
         $targetPath,
+        $autoGenerated,
         $redirectType,
         $metadata,
         $description
@@ -310,7 +335,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath)
             ->will($this->returnSelf());
-        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0)
+        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($autoGenerated)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType)
             ->will($this->returnSelf());
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
index 0e6c501c4981b6dc6e7660ee02824c7c091d4294..7bc0c96cd6e5cbdc159ac4d1ef59edb01bd5ca00 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
@@ -41,8 +41,30 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
     /** @var  ProductScopeRewriteGenerator */
     private $productScopeGenerator;
 
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */
+    private $serializer;
+
     public function setUp()
     {
+        $this->serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false);
+        $this->serializer->expects($this->any())
+            ->method('serialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_encode($value);
+                }
+            );
+        $this->serializer->expects($this->any())
+            ->method('unserialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_decode($value, true);
+                }
+            );
+
         $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator::class
         )->disableOriginalConstructor()->getMock();
@@ -61,6 +83,15 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class)
             ->disableOriginalConstructor()->getMock();
         $this->storeManager = $this->getMock(StoreManagerInterface::class);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
         $this->productScopeGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator::class,
@@ -72,6 +103,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
                 'objectRegistryFactory' => $this->objectRegistryFactory,
                 'storeViewService' => $this->storeViewService,
                 'storeManager' => $this->storeManager,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -90,35 +122,35 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
             ->method('getParentId')
             ->willReturn(1);
         $this->initObjectRegistryFactory([]);
-        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
-        $categories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $categories->setTargetPath('category-2')
+        $categories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $categories->setRequestPath('category-2')
             ->setStoreId(2);
         $this->categoriesUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$categories]));
-        $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $current->setTargetPath('category-3')
+        $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $current->setRequestPath('category-3')
             ->setStoreId(3);
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$current]));
-        $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $anchorCategories->setTargetPath('category-4')
+        $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $anchorCategories->setRequestPath('category-4')
             ->setStoreId(4);
         $this->anchorUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$anchorCategories]));
 
         $this->assertEquals(
             [
-                'category-1-1' => $canonical,
-                'category-2-2' => $categories,
-                'category-3-3' => $current,
-                'category-4-4' => $anchorCategories
+                'category-1_1' => $canonical,
+                'category-2_2' => $categories,
+                'category-3_3' => $current,
+                'category-4_4' => $anchorCategories
             ],
-            $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product)
+            $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product, 1)
         );
     }
 
@@ -137,8 +169,8 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $store->expects($this->any())->method('getRootCategoryId')->will($this->returnValue($storeRootCategoryId));
         $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store));
         $this->initObjectRegistryFactory([$category]);
-        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer);
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
@@ -150,8 +182,8 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue([]));
 
         $this->assertEquals(
-            ['category-1-1' => $canonical],
-            $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product)
+            ['category-1_1' => $canonical],
+            $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product, 1)
         );
     }
 
@@ -165,7 +197,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore')
             ->will($this->returnValue(true));
 
-        $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product));
+        $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product, 1));
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
index a392b5231b22bb87d2ad31692870ddb3fe60f7de..81eb66551007d581acaf885a9f684ca0a88451d5 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
@@ -129,6 +129,6 @@ class ProductUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->productScopeRewriteGenerator->expects($this->once())
             ->method('generateForSpecificStoreView')
             ->willReturn($urls);
-        $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock));
+        $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock, 1));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
index eb9977fa1f64fc5c8bbd216f972fc8a886efe66b..069fa1a859a7913f1eb5d1cdd610900fbaff39cb 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
@@ -23,12 +23,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var string
      */
-    protected $categoryId = 10;
+    private $categoryId = 10;
 
     /**
      * @var \Magento\UrlRewrite\Model\UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlPersist;
+    private $urlPersist;
 
     /**
      * @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -38,12 +38,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productUrlRewriteGenerator;
+    private $productUrlRewriteGenerator;
 
     /**
      * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productRepository;
+    private $productRepository;
 
     /**
      * @var \Magento\CatalogImportExport\Model\Import\Product|\PHPUnit_Framework_MockObject_MockObject
@@ -53,67 +53,65 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $observer;
+    private $observer;
 
     /**
      * @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $event;
+    private $event;
 
     /**
      * @var \Magento\Catalog\Model\ProductFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $catalogProductFactory;
+    private $catalogProductFactory;
 
     /**
      * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeManager;
+    private $storeManager;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $objectRegistryFactory;
+    private $objectRegistryFactory;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productUrlPathGenerator;
+    private $productUrlPathGenerator;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeViewService;
+    private $storeViewService;
 
     /**
      * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /**
      * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlRewrite;
+    private $urlRewrite;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $objectRegistry;
-
-    /**
-     * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $importMock;
+    private $objectRegistry;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver
      */
-    protected $import;
+    private $import;
 
     /**
      * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $product;
+    private $product;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
 
     /**
      * Test products returned by getBunch method of event object.
@@ -278,6 +276,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
             ->expects($this->any())
             ->method('getCategoryProcessor')
             ->willReturn($categoryProcessor);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
         $this->objectManager = new ObjectManager($this);
         $this->import = $this->objectManager->getObject(
@@ -291,6 +298,7 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
                 'urlPersist' => $this->urlPersist,
                 'urlRewriteFactory' => $this->urlRewriteFactory,
                 'urlFinder' => $this->urlFinder,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -433,14 +441,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
         $this->urlRewrite->expects($this->any())->method('setRequestPath')->willReturnSelf();
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->willReturnSelf();
         $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn('targetPath');
+        $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn('requestPath');
         $this->urlRewrite->expects($this->any())->method('getStoreId')
             ->willReturnOnConsecutiveCalls(0, 'not global');
 
         $this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite);
 
         $productUrls = [
-            'targetPath-0' => $this->urlRewrite,
-            'targetPath-not global' => $this->urlRewrite
+            'requestPath_0' => $this->urlRewrite,
+            'requestPath_not global' => $this->urlRewrite
         ];
 
         $this->urlPersist
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c91e485e77e13480c83b81510dfdcdf74611fa2
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
+
+use Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler;
+use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
+use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\UrlRewrite\Model\UrlPersistInterface;
+use Magento\Framework\Serialize\Serializer\Json;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+use Magento\UrlRewrite\Model\MergeDataProvider;
+
+class UrlRewriteHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var UrlRewriteHandler
+     */
+    protected $urlRewriteHandler;
+
+    /**
+     * @var ChildrenCategoriesProvider|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $childrenCategoriesProviderMock;
+
+    /**
+     * @var CategoryUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $categoryUrlRewriteGeneratorMock;
+
+    /**
+     * @var ProductUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $productUrlRewriteGeneratorMock;
+
+    /**
+     * @var UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $urlPersistMock;
+
+    /**
+     * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $collectionFactoryMock;
+
+    /**
+     * @var MergeDataProviderFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $mergeDataProviderFactoryMock;
+
+    /**
+     * @var Json|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $serializerMock;
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function setUp()
+    {
+        $this->childrenCategoriesProviderMock = $this->getMockBuilder(ChildrenCategoriesProvider::class)
+            ->getMock();
+        $this->categoryUrlRewriteGeneratorMock = $this->getMockBuilder(CategoryUrlRewriteGenerator::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->productUrlRewriteGeneratorMock = $this->getMockBuilder(ProductUrlRewriteGenerator::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->urlPersistMock = $this->getMockBuilder(UrlPersistInterface::class)
+            ->getMock();
+        $this->collectionFactoryMock = $this->getMockBuilder(CollectionFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->mergeDataProviderFactoryMock = $this->getMockBuilder(MergeDataProviderFactory::class)
+            ->setMethods(['create'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $mergeDataProviderMock = $this->getMockBuilder(MergeDataProvider::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->mergeDataProviderFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($mergeDataProviderMock);
+
+        $this->serializerMock = $this->getMockBuilder(Json::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->urlRewriteHandler = new UrlRewriteHandler(
+            $this->childrenCategoriesProviderMock,
+            $this->categoryUrlRewriteGeneratorMock,
+            $this->productUrlRewriteGeneratorMock,
+            $this->urlPersistMock,
+            $this->collectionFactoryMock,
+            $this->mergeDataProviderFactoryMock,
+            $this->serializerMock
+        );
+    }
+
+    public function testDeleteCategoryRewritesForChildren()
+    {
+        $category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $category->expects($this->once())
+            ->method('getId')
+            ->willReturn(2);
+
+        $this->childrenCategoriesProviderMock->expects($this->once())
+            ->method('getChildrenIds')
+            ->with($category, true)
+            ->willReturn([3, 4]);
+
+        $this->serializerMock->expects($this->exactly(3))
+            ->method('serialize');
+
+        $this->urlRewriteHandler->deleteCategoryRewritesForChildren($category);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/di.xml
index 5a8974fc52da4b619b73dc627ea8d35159d5ac71..7f0fc718083ecab1df47dd659bbbcac78e14a77f 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/di.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/di.xml
@@ -23,4 +23,12 @@
     <type name="Magento\UrlRewrite\Model\StorageInterface">
         <plugin name="storage_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage"/>
     </type>
+    <type name="Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder">
+        <arguments>
+            <argument name="urlRewriteClassNames" xsi:type="array">
+                <item name="product" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap</item>
+                <item name="category" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php
index 526a1d580a5d5efe0a14109345113edcc22936e9..48c6eb87c4d73755372d66d7172f2a4aa9073bd5 100644
--- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php
@@ -65,10 +65,6 @@ class ActionsTest extends \PHPUnit_Framework_TestCase
         $childNameTwo = 'child.2';
         $childNames = [$childNameOne, $childNameTwo];
 
-        $this->scopeConfigMock->expects($this->once())
-            ->method('getValue')
-            ->willReturn(false);
-
         /**
          * @var Item|\PHPUnit_Framework_MockObject_MockObject $itemMock
          */
diff --git a/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php b/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php
index cda1a6a80cce639bf511f8400d4f13119a728e5d..e938de4aad64353d54f279525b142612911963bc 100644
--- a/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php
+++ b/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php
@@ -9,6 +9,9 @@
  */
 namespace Magento\Config\Block\System\Config\Form\Fieldset\Modules;
 
+/**
+ * @deprecated
+ */
 class DisableOutput extends \Magento\Config\Block\System\Config\Form\Fieldset
 {
     /**
diff --git a/app/code/Magento/Config/Model/Config/Parser/Comment.php b/app/code/Magento/Config/Model/Config/Parser/Comment.php
new file mode 100644
index 0000000000000000000000000000000000000000..56dd38d55d800ec27c95b983d53dbb04a144dc87
--- /dev/null
+++ b/app/code/Magento/Config/Model/Config/Parser/Comment.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Config\Model\Config\Parser;
+
+use Magento\Config\Model\Placeholder\Environment;
+use Magento\Config\Model\Placeholder\PlaceholderInterface;
+use Magento\Framework\App\Config\CommentParserInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Filesystem;
+
+/**
+ * Class Comment. It is used to parse config paths from comment section.
+ */
+class Comment implements CommentParserInterface
+{
+    /**
+     * @var Filesystem
+     */
+    private $filesystem;
+
+    /**
+     * @var PlaceholderInterface
+     */
+    private $placeholder;
+
+    /**
+     * @param Filesystem $filesystem
+     * @param PlaceholderInterface $placeholder
+     */
+    public function __construct(
+        Filesystem $filesystem,
+        PlaceholderInterface $placeholder
+    ) {
+        $this->filesystem = $filesystem;
+        $this->placeholder = $placeholder;
+    }
+
+    /**
+     * Retrieves config paths from comment section of the file.
+     * Example of comment:
+     *        * CONFIG__DEFAULT__SOME__CONF__PATH_ONE
+     *        * CONFIG__DEFAULT__SOME__CONF__PATH_TWO
+     * This method will return:
+     *        array(
+     *            'CONFIG__DEFAULT__SOME__CONF__PATH_ONE' => 'some/conf/path_one',
+     *            'CONFIG__DEFAULT__SOME__CONF__PATH_TWO' => 'some/conf/path_two'
+     *        );
+     *
+     * @param string $fileName
+     * @return array
+     * @throws FileSystemException
+     */
+    public function execute($fileName)
+    {
+        $fileContent = $this->filesystem
+            ->getDirectoryRead(DirectoryList::CONFIG)
+            ->readFile($fileName);
+
+        $pattern = sprintf('/\s+\*\s+(?P<placeholder>%s.*?)\s/', preg_quote(Environment::PREFIX));
+        preg_match_all($pattern, $fileContent, $matches);
+
+        if (!isset($matches['placeholder'])) {
+            return [];
+        }
+
+        $configs = [];
+        foreach ($matches['placeholder'] as $placeholder) {
+            $path = $this->placeholder->restore($placeholder);
+            $path = preg_replace('/^' . ScopeConfigInterface::SCOPE_TYPE_DEFAULT . '\//', '', $path);
+            $configs[$placeholder] = $path;
+        }
+
+        return $configs;
+    }
+}
diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php
index 218ea75643939fb116512ced43aca46d7566e5c4..b8f420880ff6d231a36ec2e50126d36eaa149adf 100644
--- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php
+++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php
@@ -7,6 +7,7 @@ namespace Magento\Config\Test\Unit\Block\System\Config\Form\Fieldset\Modules;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @deprecated
  */
 class DisableOutputTest extends \PHPUnit_Framework_TestCase
 {
diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..933f18e1e2919ba442028feada14834313d7b617
--- /dev/null
+++ b/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Config\Test\Unit\Model\Config\Parser;
+
+use Magento\Config\Model\Config\Parser\Comment;
+use Magento\Config\Model\Placeholder\PlaceholderInterface;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+class CommentTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var PlaceholderInterface|MockObject
+     */
+    private $placeholderMock;
+
+    /**
+     * @var Filesystem|MockObject
+     */
+    private $fileSystemMock;
+
+    /**
+     * @var Comment
+     */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->placeholderMock = $this->getMockBuilder(PlaceholderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->fileSystemMock = $this->getMockBuilder(Filesystem::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->model = new Comment(
+            $this->fileSystemMock,
+            $this->placeholderMock
+        );
+    }
+
+    public function testExecute()
+    {
+        $fileName = 'config.local.php';
+        $directoryReadMock = $this->getMockBuilder(ReadInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $directoryReadMock->expects($this->once())
+            ->method('readFile')
+            ->with($fileName)
+            ->willReturn(file_get_contents(__DIR__ . '/../_files/' . $fileName));
+        $this->fileSystemMock->expects($this->once())
+            ->method('getDirectoryRead')
+            ->with(DirectoryList::CONFIG)
+            ->willReturn($directoryReadMock);
+        $this->placeholderMock->expects($this->any())
+            ->method('restore')
+            ->withConsecutive(
+                ['CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD'],
+                ['CONFIG__DEFAULT__SOME__PAYMENT__TOKEN']
+            )
+            ->willReturnOnConsecutiveCalls(
+                'some/payment/password',
+                'some/payment/token'
+            );
+
+        $this->assertEquals(
+            $this->model->execute($fileName),
+            [
+                'CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD' => 'some/payment/password',
+                'CONFIG__DEFAULT__SOME__PAYMENT__TOKEN' => 'some/payment/token'
+            ]
+        );
+    }
+}
diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php b/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c4907567b0320c163e1b0ada2c71259b30a2641
--- /dev/null
+++ b/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+return [
+    'scopes' => [
+        'websites' => [
+            'admin' => [
+                'website_id' => '0'
+            ],
+        ],
+    ],
+    /**
+     * The configuration file doesn't contain sensitive data for security reasons.
+     * Sensitive data can be stored in the following environment variables:
+     * CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD for some/payment/password
+     */
+    'system' => []
+    /**
+     * CONFIG__DEFAULT__SOME__PAYMENT__TOKEN for some/payment/token
+     * test phrase CONFIG__DEFAULT__SOME__PAYMENT__TOKEN for some/payment/token
+     */
+];
diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml
index 9579e1ca3191445edd6599eb7b8e779b36c9ab76..ab8973a1a15ceecf227d9de73450b8f906fa0c91 100644
--- a/app/code/Magento/Config/etc/di.xml
+++ b/app/code/Magento/Config/etc/di.xml
@@ -9,6 +9,7 @@
     <preference for="Magento\Config\Model\Config\Structure\SearchInterface" type="Magento\Config\Model\Config\Structure" />
     <preference for="Magento\Config\Model\Config\Backend\File\RequestData\RequestDataInterface" type="Magento\Config\Model\Config\Backend\File\RequestData" />
     <preference for="Magento\Framework\App\Config\ConfigResource\ConfigInterface" type="Magento\Config\Model\ResourceModel\Config" />
+    <preference for="Magento\Framework\App\Config\CommentParserInterface" type="Magento\Config\Model\Config\Parser\Comment" />
     <virtualType name="Magento\Framework\View\TemplateEngine\Xhtml\ConfigCompiler" type="Magento\Framework\View\TemplateEngine\Xhtml\Compiler" shared="false">
         <arguments>
             <argument name="compilerText" xsi:type="object">Magento\Framework\View\TemplateEngine\Xhtml\Compiler\Text</argument>
@@ -184,4 +185,9 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Config\Model\Config\Parser\Comment">
+        <arguments>
+            <argument name="placeholder" xsi:type="object">Magento\Config\Model\Placeholder\Environment</argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml
index 540918d44c1394892b441bb57eba07edd67706e7..ccee6914e4ea094dc4a4b664e41ee22ea7457d2c 100644
--- a/app/code/Magento/Customer/etc/config.xml
+++ b/app/code/Magento/Customer/etc/config.xml
@@ -82,17 +82,15 @@ T: {{var telephone}}
 {{depend vat_id}}<br/>VAT: {{var vat_id}}{{/depend}}]]></html>
                 <pdf><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}|
 {{depend company}}{{var company}}|{{/depend}}
-{{if street1}}{{var street1}}
-{{/if}}
+{{if street1}}{{var street1}}|{{/if}}
 {{depend street2}}{{var street2}}|{{/depend}}
 {{depend street3}}{{var street3}}|{{/depend}}
 {{depend street4}}{{var street4}}|{{/depend}}
-{{if city}}{{var city}},|{{/if}}
-{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}|
+{{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}|
 {{var country}}|
-{{depend telephone}}T: {{var telephone}}{{/depend}}|
-{{depend fax}}<br/>F: {{var fax}}{{/depend}}|
-{{depend vat_id}}<br/>VAT: {{var vat_id}}{{/depend}}|]]></pdf>
+{{depend telephone}}T: {{var telephone}}|{{/depend}}
+{{depend fax}}F: {{var fax}}|{{/depend}}|
+{{depend vat_id}}VAT: {{var vat_id}}{{/depend}}|]]></pdf>
             </address_templates>
         </customer>
     </default>
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..e78b4b71d31184a3f44439d4a168766475b9ad09
--- /dev/null
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Class CollectorFactory creates instance of CollectorInterface.
+ */
+class CollectorFactory
+{
+    /**#@+
+     * Constant for collector types.
+     */
+    const TYPE_INTERACTIVE = 'interactive';
+    const TYPE_SIMPLE = 'simple';
+    /**#@-*/
+
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * @var array
+     */
+    private $types;
+
+    /**
+     * @param ObjectManagerInterface $objectManager
+     * @param array $types
+     */
+    public function __construct(
+        ObjectManagerInterface $objectManager,
+        array $types = []
+    ) {
+        $this->objectManager = $objectManager;
+        $this->types = $types;
+    }
+
+    /**
+     * Create instance of CollectorInterface by given type.
+     *
+     * @param string $type
+     * @return CollectorInterface
+     * @throws LocalizedException If collector type not exist in registered types array.
+     */
+    public function create($type)
+    {
+        if (!isset($this->types[$type])) {
+            throw new LocalizedException(__('Class for type "%1" was not declared', $type));
+        }
+
+        $object = $this->objectManager->create($this->types[$type]);
+
+        if (!$object instanceof CollectorInterface) {
+            throw new LocalizedException(
+                __('%1 does not implement %2', get_class($object), CollectorInterface::class)
+            );
+        }
+
+        return $object;
+    }
+}
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..6aecf23adf37d494ba02ae46f2e09bd52fd1079f
--- /dev/null
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Framework\Exception\LocalizedException;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Interface CollectorInterface
+ */
+interface CollectorInterface
+{
+    /**
+     * Collects values from user input and return result as array.
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @param array $configPaths list of available config paths.
+     * @return array
+     * @throws LocalizedException
+     */
+    public function getValues(InputInterface $input, OutputInterface $output, array $configPaths);
+}
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f67a98ff1c58d49dba645e4987c833d840260d2
--- /dev/null
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\QuestionFactory;
+use Symfony\Component\Console\Helper\QuestionHelper;
+
+/**
+ * Class InteractiveCollector collects configuration values from user input.
+ */
+class InteractiveCollector implements CollectorInterface
+{
+    /**
+     * @var QuestionFactory
+     */
+    private $questionFactory;
+
+    /**
+     * @var QuestionHelper
+     */
+    private $questionHelper;
+
+    /**
+     * @param QuestionFactory $questionFactory
+     * @param QuestionHelper $questionHelper
+     */
+    public function __construct(
+        QuestionFactory $questionFactory,
+        QuestionHelper $questionHelper
+    ) {
+        $this->questionFactory = $questionFactory;
+        $this->questionHelper = $questionHelper;
+    }
+
+    /**
+     * Collect list of configuration values from user input.
+     * For example, this method will return
+     *
+     * ```php
+     * [
+     *     'some/configuration/path1' => 'someValue1',
+     *     'some/configuration/path2' => 'someValue2',
+     *     'some/configuration/path3' => 'someValue3',
+     * ]
+     * ```
+     *
+     * {@inheritdoc}
+     */
+    public function getValues(InputInterface $input, OutputInterface $output, array $configPaths)
+    {
+        $output->writeln('<info>Please set configuration values or skip them by pressing [Enter]:</info>');
+        $values = [];
+        foreach ($configPaths as $configPath) {
+            $question = $this->questionFactory->create([
+                'question' => $configPath . ': '
+            ]);
+            $values[$configPath] = $this->questionHelper->ask($input, $output, $question);
+        }
+
+        return $values;
+    }
+}
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php
new file mode 100644
index 0000000000000000000000000000000000000000..828b4254a53085a986c1b0933912bae3efe7bc0c
--- /dev/null
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Phrase;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Question\QuestionFactory;
+use Symfony\Component\Console\Helper\QuestionHelper;
+
+/**
+ * Class SimpleCollector collects configuration value from user input.
+ */
+class SimpleCollector implements CollectorInterface
+{
+    /**
+     * @var QuestionFactory
+     */
+    private $questionFactory;
+
+    /**
+     * @var QuestionHelper
+     */
+    private $questionHelper;
+
+    /**
+     * @param QuestionFactory $questionFactory
+     * @param QuestionHelper $questionHelper
+     */
+    public function __construct(
+        QuestionFactory $questionFactory,
+        QuestionHelper $questionHelper
+    ) {
+        $this->questionFactory = $questionFactory;
+        $this->questionHelper = $questionHelper;
+    }
+
+    /**
+     * Collects single configuration value from user input.
+     * For example, this method will return
+     *
+     * ```php
+     * ['some/configuration/path' => 'someValue']
+     * ```
+     *
+     * {@inheritdoc}
+     */
+    public function getValues(InputInterface $input, OutputInterface $output, array $configPaths)
+    {
+        $inputPath = $input->getArgument(SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH);
+        $configPathQuestion = $this->getConfigPathQuestion($configPaths);
+        $configPath = $inputPath === null ?
+            $this->questionHelper->ask($input, $output, $configPathQuestion) :
+            $inputPath;
+
+        $this->validatePath($configPath, $configPaths);
+
+        $inputValue = $input->getArgument(SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE);
+        $configValueQuestion = $this->getConfigValueQuestion();
+        $configValue = $inputValue === null ?
+            $this->questionHelper->ask($input, $output, $configValueQuestion) :
+            $inputValue;
+
+        return [$configPath => $configValue];
+    }
+
+    /**
+     * Get Question to fill configuration path with autocompletion in interactive mode.
+     *
+     * @param array $configPaths
+     * @return Question
+     */
+    private function getConfigPathQuestion(array $configPaths)
+    {
+        /** @var Question $configPathQuestion */
+        $configPathQuestion = $this->questionFactory->create([
+            'question' => 'Please enter config path: '
+        ]);
+        $configPathQuestion->setAutocompleterValues($configPaths);
+        $configPathQuestion->setValidator(function ($configPath) use ($configPaths) {
+            $this->validatePath($configPath, $configPaths);
+            return $configPath;
+        });
+
+        return $configPathQuestion;
+    }
+
+    /**
+     * Get Question to fill configuration value in interactive mode.
+     *
+     * @return Question
+     */
+    private function getConfigValueQuestion()
+    {
+        /** @var Question $configValueQuestion */
+        $configValueQuestion = $this->questionFactory->create([
+            'question' => 'Please enter value: '
+        ]);
+        $configValueQuestion->setValidator(function ($interviewer) {
+            if (empty($interviewer)) {
+                throw new LocalizedException(new Phrase('Value can\'t be empty'));
+            }
+            return $interviewer;
+        });
+
+        return $configValueQuestion;
+    }
+
+    /**
+     * Check if entered configuration path is valid, throw LocalizedException otherwise.
+     *
+     * @param string $configPath Path that should be validated.
+     * @param array $configPaths List of allowed paths.
+     * @return void
+     * @throws LocalizedException If config path not exist in allowed config paths.
+     */
+    private function validatePath($configPath, array $configPaths)
+    {
+        if (!in_array($configPath, $configPaths)) {
+            throw new LocalizedException(
+                new Phrase('A configuration with this path does not exist or is not sensitive')
+            );
+        }
+    }
+}
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8555ceb9530f59561dce02ed264b3f0de3ee72b
--- /dev/null
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory;
+use Magento\Deploy\Model\ConfigWriter;
+use Magento\Framework\App\Config\CommentParserInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Scope\ValidatorInterface;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Console\Cli;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Exception\LocalizedException;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Command for set sensitive variable through deploy process.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class SensitiveConfigSetCommand extends Command
+{
+    /**#@+
+     * Names of input arguments or options.
+     */
+    const INPUT_OPTION_INTERACTIVE = 'interactive';
+    const INPUT_OPTION_SCOPE = 'scope';
+    const INPUT_OPTION_SCOPE_CODE = 'scope-code';
+    const INPUT_ARGUMENT_PATH = 'path';
+    const INPUT_ARGUMENT_VALUE = 'value';
+    /**#@-*/
+
+    /**
+     * @var CommentParserInterface
+     */
+    private $commentParser;
+
+    /**
+     * @var ConfigFilePool
+     */
+    private $configFilePool;
+
+    /**
+     * @var ConfigWriter
+     */
+    private $configWriter;
+
+    /**
+     * @var ValidatorInterface
+     */
+    private $scopeValidator;
+
+    /**
+     * @var CollectorFactory
+     */
+    private $collectorFactory;
+
+    /**
+     * @param ConfigFilePool $configFilePool
+     * @param CommentParserInterface $commentParser
+     * @param ConfigWriter $configWriter
+     * @param ValidatorInterface $scopeValidator
+     * @param CollectorFactory $collectorFactory
+     */
+    public function __construct(
+        ConfigFilePool $configFilePool,
+        CommentParserInterface $commentParser,
+        ConfigWriter $configWriter,
+        ValidatorInterface $scopeValidator,
+        CollectorFactory $collectorFactory
+    ) {
+        parent::__construct();
+        $this->commentParser = $commentParser;
+        $this->configFilePool = $configFilePool;
+        $this->configWriter = $configWriter;
+        $this->scopeValidator = $scopeValidator;
+        $this->collectorFactory = $collectorFactory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this->addArgument(
+            self::INPUT_ARGUMENT_PATH,
+            InputArgument::OPTIONAL,
+            'Configuration path for example group/section/field_name'
+        );
+        $this->addArgument(
+            self::INPUT_ARGUMENT_VALUE,
+            InputArgument::OPTIONAL,
+            'Configuration value'
+        );
+        $this->addOption(
+            self::INPUT_OPTION_INTERACTIVE,
+            'i',
+            InputOption::VALUE_NONE,
+            'Enable interactive mode to set all sensitive variables'
+        );
+        $this->addOption(
+            self::INPUT_OPTION_SCOPE,
+            null,
+            InputOption::VALUE_OPTIONAL,
+            'Scope for configuration, if not set use \'default\'',
+            ScopeConfigInterface::SCOPE_TYPE_DEFAULT
+        );
+        $this->addOption(
+            self::INPUT_OPTION_SCOPE_CODE,
+            null,
+            InputOption::VALUE_OPTIONAL,
+            'Scope code for configuration, empty string by default',
+            ''
+        );
+        $this->setName('config:sensitive:set')
+            ->setDescription('Set sensitive configuration values');
+        parent::configure();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $scope = $input->getOption(self::INPUT_OPTION_SCOPE);
+        $scopeCode = $input->getOption(self::INPUT_OPTION_SCOPE_CODE);
+
+        try {
+            $this->scopeValidator->isValid($scope, $scopeCode);
+            $configPaths = $this->getConfigPaths();
+            $isInteractive = $input->getOption(self::INPUT_OPTION_INTERACTIVE);
+            $collector = $this->collectorFactory->create(
+                $isInteractive ? CollectorFactory::TYPE_INTERACTIVE : CollectorFactory::TYPE_SIMPLE
+            );
+            $values = $collector->getValues($input, $output, $configPaths);
+            $this->configWriter->save($values, $scope, $scopeCode);
+        } catch (LocalizedException $e) {
+            $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
+            return Cli::RETURN_FAILURE;
+        }
+
+        $this->writeSuccessMessage($output, $isInteractive);
+
+        return Cli::RETURN_SUCCESS;
+    }
+
+    /**
+     * Writes success message.
+     *
+     * @param OutputInterface $output
+     * @param boolean $isInteractive
+     * @return void
+     */
+    private function writeSuccessMessage(OutputInterface $output, $isInteractive)
+    {
+        $output->writeln(sprintf(
+            '<info>Configuration value%s saved in app/etc/%s</info>',
+            $isInteractive ? 's' : '',
+            $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG)
+        ));
+    }
+
+    /**
+     * Get sensitive configuration paths.
+     *
+     * @return array
+     * @throws LocalizedException if configuration file not exists or sensitive configuration is empty
+     */
+    private function getConfigPaths()
+    {
+        $configFilePath = $this->configFilePool->getPathsByPool(ConfigFilePool::LOCAL)[ConfigFilePool::APP_CONFIG];
+        try {
+            $configPaths = $this->commentParser->execute($configFilePath);
+        } catch (FileSystemException $e) {
+            throw new LocalizedException(__(
+                'File app/etc/%1 can\'t be read. Please check if it exists and has read permissions.',
+                [
+                    $configFilePath
+                ]
+            ));
+        }
+
+        if (empty($configPaths)) {
+            throw new LocalizedException(__('There are no sensitive configurations to fill'));
+        }
+
+        return $configPaths;
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/ConfigWriter.php b/app/code/Magento/Deploy/Model/ConfigWriter.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a9b70c528ac9e5d9010e0683b651823ecb74c4a
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/ConfigWriter.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Model;
+
+use Magento\Config\App\Config\Type\System;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\DeploymentConfig\Writer;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Stdlib\ArrayManager;
+
+/**
+ * Class ConfigWriter. Save configuration values into config file.
+ */
+class ConfigWriter
+{
+    /**
+     * @var Writer
+     */
+    private $writer;
+
+    /**
+     * @var ArrayManager
+     */
+    private $arrayManager;
+
+    /**
+     * @param Writer $writer
+     * @param ArrayManager $arrayManager
+     */
+    public function __construct(
+        Writer $writer,
+        ArrayManager $arrayManager
+    ) {
+        $this->writer = $writer;
+        $this->arrayManager = $arrayManager;
+    }
+
+    /**
+     * Save given list of configuration values into config file.
+     *
+     * @param array $values the configuration values (path-value pairs) to be saved.
+     * @param string $scope scope in which configuration would be saved.
+     * @param string|null $scopeCode
+     * @return void
+     */
+    public function save(array $values, $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null)
+    {
+        $config = [];
+        $pathPrefix = $this->getPathPrefix($scope, $scopeCode);
+        foreach ($values as $configPath => $configValue) {
+            $fullConfigPath = $pathPrefix . $configPath;
+            $config = $this->setConfig($config, $fullConfigPath, $configValue);
+        }
+
+        $this->writer
+            ->saveConfig(
+                [ConfigFilePool::APP_CONFIG => $config]
+            );
+    }
+
+    /**
+     * Apply configuration value into configuration array by given path.
+     * Ignore values that equal to null.
+     *
+     * @param array $config
+     * @param string $configPath
+     * @param string $configValue
+     * @return array
+     */
+    private function setConfig(array $config, $configPath, $configValue)
+    {
+        if ($configValue === null) {
+            return $config;
+        }
+
+        $config = $this->arrayManager->set(
+            $configPath,
+            $config,
+            $configValue
+        );
+
+        return $config;
+    }
+
+    /**
+     * Generate config prefix from given $scope and $scopeCode.
+     * If $scope isn't equal to 'default' and $scopeCode isn't empty put $scopeCode into prefix path,
+     * otherwise ignore $scopeCode.
+     *
+     * @param string $scope
+     * @param string $scopeCode
+     * @return string
+     */
+    private function getPathPrefix($scope, $scopeCode)
+    {
+        $pathPrefixes = [System::CONFIG_TYPE, $scope];
+        if (
+            $scope !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT
+            && !empty($scopeCode)
+        ) {
+            $pathPrefixes[] = $scopeCode;
+        }
+
+        return implode('/', $pathPrefixes) . '/';
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/Filesystem.php b/app/code/Magento/Deploy/Model/Filesystem.php
index 83de641c4442a445cceb020cad060ce2c4809f5b..7918207934759bd1cb003e676c3b74a41330777c 100644
--- a/app/code/Magento/Deploy/Model/Filesystem.php
+++ b/app/code/Magento/Deploy/Model/Filesystem.php
@@ -13,7 +13,8 @@ use Magento\Framework\Exception\LocalizedException;
 use Magento\User\Model\ResourceModel\User\Collection as UserCollection;
 
 /**
- * Generate static files, compile; clear var/generation, var/di/, var/view_preprocessed and pub/static directories
+ * Generate static files, compile
+ * Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories
  *
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
@@ -129,7 +130,7 @@ class Filesystem
     public function regenerateStatic(
         OutputInterface $output
     ) {
-        // Сlear var/generation, var/di/, var/view_preprocessed and pub/static directories
+        // Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories
         $this->cleanupFilesystem(
             [
                 DirectoryList::CACHE,
@@ -315,7 +316,7 @@ class Filesystem
      */
     public function lockStaticResources()
     {
-        // Lock /var/generation, /var/di/ and /var/view_preprocessed directories
+        // Lock /generated/code, /generated/metadata/ and /var/view_preprocessed directories
         $this->changePermissions(
             [
                 DirectoryList::GENERATION,
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c16fa1d7c176db14d525444a3f18e2231258a31f
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorInterface;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector;
+use Magento\Framework\ObjectManagerInterface;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use stdClass;
+
+class CollectorFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ObjectManagerInterface|MockObject
+     */
+    private $objectManagerMock;
+
+    /**
+     * @var CollectorFactory
+     */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)
+            ->getMockForAbstractClass();
+
+        $this->model = new CollectorFactory(
+            $this->objectManagerMock,
+            [
+                CollectorFactory::TYPE_SIMPLE => SimpleCollector::class,
+                CollectorFactory::TYPE_INTERACTIVE => InteractiveCollector::class,
+                'wrongType' => stdClass::class,
+            ]
+        );
+    }
+
+    public function testCreate()
+    {
+        $collectorMock = $this->getMockBuilder(CollectorInterface::class)
+            ->getMockForAbstractClass();
+        $this->objectManagerMock->expects($this->once())
+            ->method('create')
+            ->with(SimpleCollector::class)
+            ->willReturn($collectorMock);
+
+        $this->assertInstanceOf(
+            CollectorInterface::class,
+            $this->model->create(CollectorFactory::TYPE_SIMPLE)
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Class for type "dummyType" was not declared
+     */
+    public function testCreateNonExisted()
+    {
+        $this->model->create('dummyType');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage stdClass does not implement
+     */
+    public function testCreateWrongImplementation()
+    {
+        $type = 'wrongType';
+        $this->objectManagerMock->expects($this->once())
+            ->method('create')
+            ->with(stdClass::class)
+            ->willReturn(new stdClass());
+
+        $this->model->create($type);
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..91576c89463726db1e0f034e72ac96e418770083
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Question\QuestionFactory;
+use Symfony\Component\Console\Helper\QuestionHelper;
+
+class InteractiveCollectorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var QuestionFactory|MockObject
+     */
+    private $questionFactoryMock;
+
+    /**
+     * @var QuestionHelper|MockObject
+     */
+    private $questionHelperMock;
+
+    /**
+     * @var InputInterface|MockObject
+     */
+    private $inputMock;
+
+    /**
+     * @var OutputInterface|MockObject
+     */
+    private $outputMock;
+
+    /**
+     * @var InteractiveCollector
+     */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->questionFactoryMock = $this->getMockBuilder(QuestionFactory::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['create'])
+            ->getMock();
+        $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->inputMock = $this->getMockBuilder(InputInterface::class)
+            ->getMockForAbstractClass();
+        $this->outputMock = $this->getMockBuilder(OutputInterface::class)
+            ->getMockForAbstractClass();
+
+        $this->model = new InteractiveCollector(
+            $this->questionFactoryMock,
+            $this->questionHelperMock
+        );
+    }
+
+    public function testGetValues()
+    {
+        $configPaths = [
+            'some/config/path1',
+            'some/config/path2',
+            'some/config/path3'
+        ];
+
+        $questionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->questionHelperMock->expects($this->exactly(3))
+            ->method('ask')
+            ->with($this->inputMock, $this->outputMock, $questionMock)
+            ->willReturn('someValue');
+        $this->questionFactoryMock->expects($this->exactly(3))
+            ->method('create')
+            ->withConsecutive(
+                [['question' => $configPaths[0] . ': ']],
+                [['question' => $configPaths[1] . ': ']],
+                [['question' => $configPaths[2] . ': ']]
+            )
+            ->willReturn($questionMock);
+
+        $this->assertEquals(
+            [
+                'some/config/path1' => 'someValue',
+                'some/config/path2' => 'someValue',
+                'some/config/path3' => 'someValue'
+            ],
+            $this->model->getValues(
+                $this->inputMock,
+                $this->outputMock,
+                $configPaths
+            )
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..124fc4b0119e377f508e8b1b22439446bd9d8fe4
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand;
+use Magento\Framework\Exception\LocalizedException;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Question\QuestionFactory;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+class SimpleCollectorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var QuestionFactory|MockObject
+     */
+    private $questionFactoryMock;
+
+    /**
+     * @var QuestionHelper|MockObject
+     */
+    private $questionHelperMock;
+
+    /**
+     * @var InputInterface|MockObject
+     */
+    private $inputMock;
+
+    /**
+     * @var OutputInterface|MockObject
+     */
+    private $outputMock;
+
+    /**
+     * @var SimpleCollector
+     */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->questionFactoryMock = $this->getMockBuilder(QuestionFactory::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['create'])
+            ->getMock();
+        $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->inputMock = $this->getMockBuilder(InputInterface::class)
+            ->getMockForAbstractClass();
+        $this->outputMock = $this->getMockBuilder(OutputInterface::class)
+            ->getMockForAbstractClass();
+
+        $this->model = new SimpleCollector(
+            $this->questionFactoryMock,
+            $this->questionHelperMock
+        );
+    }
+
+    public function testGetValues()
+    {
+        $configPaths = [
+            'some/config/path1',
+            'some/config/path2'
+        ];
+
+        $pathQuestionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $valueQuestionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->inputMock->expects($this->exactly(2))
+            ->method('getArgument')
+            ->withConsecutive(
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH],
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $configPaths[0],
+                'someValue'
+            );
+        $this->questionFactoryMock->expects($this->exactly(2))
+            ->method('create')
+            ->withConsecutive(
+                [['question' => 'Please enter config path: ']],
+                [['question' => 'Please enter value: ']]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $pathQuestionMock,
+                $valueQuestionMock
+            );
+
+        $this->assertEquals(
+            ['some/config/path1' => 'someValue'],
+            $this->model->getValues(
+                $this->inputMock,
+                $this->outputMock,
+                $configPaths
+            )
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage A configuration with this path does not exist or is not sensitive
+     */
+    public function testWrongConfigPath()
+    {
+        $configPaths = [
+            'some/config/path1',
+            'some/config/path2'
+        ];
+
+        $pathQuestionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->inputMock->expects($this->once())
+            ->method('getArgument')
+            ->with(SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH)
+            ->willReturn('some/not_exist/config');
+        $this->questionFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(['question' => 'Please enter config path: '])
+            ->willReturn($pathQuestionMock);
+
+        $this->model->getValues(
+            $this->inputMock,
+            $this->outputMock,
+            $configPaths
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     */
+    public function testEmptyValue()
+    {
+        $configPaths = [
+            'some/config/path1',
+            'some/config/path2'
+        ];
+        $message = 'exception message';
+
+        $pathQuestionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $valueQuestionMock = $this->getMockBuilder(Question::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->questionHelperMock->expects($this->once())
+            ->method('ask')
+            ->with($this->inputMock, $this->outputMock, $valueQuestionMock)
+            ->willThrowException(new LocalizedException(__($message)));
+        $this->inputMock->expects($this->exactly(2))
+            ->method('getArgument')
+            ->withConsecutive(
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH],
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $configPaths[0],
+                null
+            );
+        $this->questionFactoryMock->expects($this->exactly(2))
+            ->method('create')
+            ->withConsecutive(
+                [['question' => 'Please enter config path: ']],
+                [['question' => 'Please enter value: ']]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $pathQuestionMock,
+                $valueQuestionMock
+            );
+
+        $this->model->getValues(
+            $this->inputMock,
+            $this->outputMock,
+            $configPaths
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b305ed8249fbff8d80854385f1f3fa79d1204541
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Console\Command\App;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorInterface;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand;
+use Magento\Deploy\Model\ConfigWriter;
+use Magento\Framework\App\Config\CommentParserInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Scope\ValidatorInterface;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Console\Cli;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Phrase;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use Symfony\Component\Console\Tester\CommandTester;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class SensitiveConfigSetCommandTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ConfigFilePool|MockObject
+     */
+    private $configFilePoolMock;
+
+    /**
+     * @var CommentParserInterface|MockObject
+     */
+    private $commentParserMock;
+
+    /**
+     * @var ConfigWriter|MockObject
+     */
+    private $configWriterMock;
+
+    /**
+     * @var ValidatorInterface|MockObject
+     */
+    private $scopeValidatorMock;
+
+    /**
+     * @var CollectorFactory|MockObject
+     */
+    private $collectorFactoryMock;
+
+    /**
+     * @var SensitiveConfigSetCommand
+     */
+    private $command;
+
+    public function setUp()
+    {
+        $this->configFilePoolMock = $this->getMockBuilder(ConfigFilePool::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->commentParserMock = $this->getMockBuilder(CommentParserInterface::class)
+            ->getMockForAbstractClass();
+        $this->configWriterMock = $this->getMockBuilder(ConfigWriter::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->scopeValidatorMock = $this->getMockBuilder(ValidatorInterface::class)
+            ->getMockForAbstractClass();
+        $this->collectorFactoryMock = $this->getMockBuilder(CollectorFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->command = new SensitiveConfigSetCommand(
+            $this->configFilePoolMock,
+            $this->commentParserMock,
+            $this->configWriterMock,
+            $this->scopeValidatorMock,
+            $this->collectorFactoryMock
+        );
+    }
+
+    public function testConfigFileNotExist()
+    {
+        $this->configFilePoolMock->expects($this->once())
+            ->method('getPathsByPool')
+            ->with(ConfigFilePool::LOCAL)
+            ->willReturn([
+                ConfigFilePool::APP_CONFIG => 'config.local.php'
+            ]);
+        $this->scopeValidatorMock->expects($this->once())
+            ->method('isValid')
+            ->with('default', '')
+            ->willReturn(true);
+        $this->commentParserMock->expects($this->any())
+            ->method('execute')
+            ->willThrowException(new FileSystemException(new Phrase('some message')));
+
+        $tester = new CommandTester($this->command);
+        $tester->execute([
+            'path' => 'some/path',
+            'value' => 'some value'
+        ]);
+
+        $this->assertEquals(
+            Cli::RETURN_FAILURE,
+            $tester->getStatusCode()
+        );
+        $this->assertContains(
+            'File app/etc/config.local.php can\'t be read. '
+            . 'Please check if it exists and has read permissions.',
+            $tester->getDisplay()
+        );
+    }
+
+    public function testWriterException()
+    {
+        $exceptionMessage = 'exception';
+        $this->scopeValidatorMock->expects($this->once())
+            ->method('isValid')
+            ->with('default', '')
+            ->willReturn(true);
+        $this->commentParserMock->expects($this->once())
+            ->method('execute')
+            ->willReturn([
+                'some/config/path1',
+                'some/config/path2'
+            ]);
+        $collectorMock = $this->getMockBuilder(CollectorInterface::class)
+            ->getMockForAbstractClass();
+        $collectorMock->expects($this->once())
+            ->method('getValues')
+            ->willReturn(['some/config/pathNotExist' => 'value']);
+        $this->collectorFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(CollectorFactory::TYPE_SIMPLE)
+            ->willReturn($collectorMock);
+        $this->configWriterMock->expects($this->once())
+            ->method('save')
+            ->willThrowException(new LocalizedException(__($exceptionMessage)));
+
+        $tester = new CommandTester($this->command);
+        $tester->execute([
+            'path' => 'some/config/pathNotExist',
+            'value' => 'some value'
+        ]);
+
+        $this->assertEquals(
+            Cli::RETURN_FAILURE,
+            $tester->getStatusCode()
+        );
+        $this->assertContains(
+            $exceptionMessage,
+            $tester->getDisplay()
+        );
+    }
+
+    public function testEmptyConfigPaths()
+    {
+        $this->scopeValidatorMock->expects($this->once())
+            ->method('isValid')
+            ->with('default', '')
+            ->willReturn(true);
+        $this->commentParserMock->expects($this->once())
+            ->method('execute')
+            ->willReturn([]);
+
+        $tester = new CommandTester($this->command);
+        $tester->execute([
+            'path' => 'some/config/pathNotExist',
+            'value' => 'some value'
+        ]);
+
+        $this->assertEquals(
+            Cli::RETURN_FAILURE,
+            $tester->getStatusCode()
+        );
+        $this->assertContains(
+            'There are no sensitive configurations to fill',
+            $tester->getDisplay()
+        );
+    }
+
+    public function testExecute()
+    {
+        $collectedValues = ['some/config/path1' => 'value'];
+        $this->scopeValidatorMock->expects($this->once())
+            ->method('isValid')
+            ->with('default', '')
+            ->willReturn(true);
+        $this->commentParserMock->expects($this->once())
+            ->method('execute')
+            ->willReturn([
+                'some/config/path1',
+                'some/config/path2'
+            ]);
+        $collectorMock = $this->getMockBuilder(CollectorInterface::class)
+            ->getMockForAbstractClass();
+        $collectorMock->expects($this->once())
+            ->method('getValues')
+            ->willReturn($collectedValues);
+        $this->collectorFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(CollectorFactory::TYPE_SIMPLE)
+            ->willReturn($collectorMock);
+        $this->configWriterMock->expects($this->once())
+            ->method('save')
+            ->with($collectedValues, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, '');
+
+        $tester = new CommandTester($this->command);
+        $tester->execute([]);
+
+        $this->assertEquals(
+            Cli::RETURN_SUCCESS,
+            $tester->getStatusCode()
+        );
+        $this->assertContains(
+            'Configuration value saved in',
+            $tester->getDisplay()
+        );
+    }
+
+    public function testExecuteInteractive()
+    {
+        $collectedValues = ['some/config/path1' => 'value'];
+        $this->scopeValidatorMock->expects($this->once())
+            ->method('isValid')
+            ->with('default', '')
+            ->willReturn(true);
+        $this->commentParserMock->expects($this->once())
+            ->method('execute')
+            ->willReturn([
+                'some/config/path1',
+                'some/config/path2',
+                'some/config/path3'
+            ]);
+        $collectorMock = $this->getMockBuilder(CollectorInterface::class)
+            ->getMockForAbstractClass();
+        $collectorMock->expects($this->once())
+            ->method('getValues')
+            ->willReturn($collectedValues);
+        $this->collectorFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(CollectorFactory::TYPE_INTERACTIVE)
+            ->willReturn($collectorMock);
+        $this->configWriterMock->expects($this->once())
+            ->method('save')
+            ->with($collectedValues, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, '');
+
+        $tester = new CommandTester($this->command);
+        $tester->execute(['--interactive' => true]);
+
+        $this->assertEquals(
+            Cli::RETURN_SUCCESS,
+            $tester->getStatusCode()
+        );
+        $this->assertContains(
+            'Configuration values saved in',
+            $tester->getDisplay()
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ac0097d5f73b69ea9bf02cafa7cd4773d9a810e
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model;
+
+use Magento\Deploy\Model\ConfigWriter;
+use Magento\Framework\App\DeploymentConfig\Writer;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Stdlib\ArrayManager;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+class ConfigWriterTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Writer|MockObject
+     */
+    private $writerMock;
+
+    /**
+     * @var ArrayManager|MockObject
+     */
+    private $arrayManagerMock;
+
+    /**
+     * @var ConfigWriter
+     */
+    private $model;
+
+    public function setUp()
+    {
+        $this->arrayManagerMock = $this->getMockBuilder(ArrayManager::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->writerMock = $this->getMockBuilder(Writer::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->model = new ConfigWriter(
+            $this->writerMock,
+            $this->arrayManagerMock
+        );
+    }
+
+    public function testSave()
+    {
+        $values = [
+            'some1/config1/path1' => 'someValue1',
+            'some2/config2/path2' => 'someValue2',
+            'some3/config3/path3' => 'someValue3'
+        ];
+        $config = ['system' => []];
+
+        $this->arrayManagerMock->expects($this->exactly(3))
+            ->method('set')
+            ->withConsecutive(
+                ['system/scope/scope_code/some1/config1/path1', $this->anything(), 'someValue1'],
+                ['system/scope/scope_code/some2/config2/path2', $this->anything(), 'someValue2'],
+                ['system/scope/scope_code/some3/config3/path3', $this->anything(), 'someValue3']
+            )
+            ->willReturn($config);
+        $this->writerMock->expects($this->once())
+            ->method('saveConfig')
+            ->with([ConfigFilePool::APP_CONFIG => $config]);
+
+        $this->model->save($values, 'scope', 'scope_code');
+    }
+
+    public function testSaveDefaultScope()
+    {
+        $values = [
+            'some1/config1/path1' => 'someValue1',
+            'some2/config2/path2' => 'someValue2',
+            'some3/config3/path3' => 'someValue3'
+        ];
+        $config = ['system' => []];
+
+        $this->arrayManagerMock->expects($this->exactly(3))
+            ->method('set')
+            ->withConsecutive(
+                ['system/default/some1/config1/path1', $this->anything(), 'someValue1'],
+                ['system/default/some2/config2/path2', $this->anything(), 'someValue2'],
+                ['system/default/some3/config3/path3', $this->anything(), 'someValue3']
+            )
+            ->willReturn($config);
+        $this->writerMock->expects($this->once())
+            ->method('saveConfig')
+            ->with([ConfigFilePool::APP_CONFIG => $config]);
+
+        $this->model->save($values);
+    }
+}
diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json
index ed0b8520ca645afa8af2506616f9dccde6965f08..436c3a6451c39b3e65ae65e3740b402bbc75fe80 100644
--- a/app/code/Magento/Deploy/composer.json
+++ b/app/code/Magento/Deploy/composer.json
@@ -6,7 +6,8 @@
         "magento/framework": "100.2.*",
         "magento/module-store": "100.2.*",
         "magento/module-require-js": "100.2.*",
-        "magento/module-user": "100.2.*"
+        "magento/module-user": "100.2.*",
+        "magento/module-config": "100.2.*"
     },
     "type": "magento2-module",
     "version": "100.2.0-dev",
diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml
index 68fa7d909e4df8a3b13f3ef01a8f178d804feb4d..6751dd1e0b161dfee948436818a7f38c50025daf 100644
--- a/app/code/Magento/Deploy/etc/di.xml
+++ b/app/code/Magento/Deploy/etc/di.xml
@@ -26,6 +26,7 @@
                 <item name="setModeCommand" xsi:type="object">Magento\Deploy\Console\Command\SetModeCommand</item>
                 <item name="showModeCommand" xsi:type="object">Magento\Deploy\Console\Command\ShowModeCommand</item>
                 <item name="dumpApplicationCommand" xsi:type="object">\Magento\Deploy\Console\Command\App\ApplicationDumpCommand</item>
+                <item name="sensitiveConfigSetCommand" xsi:type="object">\Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand</item>
             </argument>
         </arguments>
     </type>
@@ -34,4 +35,12 @@
             <argument name="shell" xsi:type="object">Magento\Framework\App\Shell</argument>
         </arguments>
     </type>
+    <type name="Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory">
+        <arguments>
+            <argument name="types" xsi:type="array">
+                <item name="interactive" xsi:type="string">Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector</item>
+                <item name="simple" xsi:type="string">Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Fedex/etc/di.xml b/app/code/Magento/Fedex/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e2f5142cde96b5f82c10175da290e680e79f419c
--- /dev/null
+++ b/app/code/Magento/Fedex/etc/di.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
+    <type name="Magento\Config\Model\Config\Export\ExcludeList">
+        <arguments>
+            <argument name="configs" xsi:type="array">
+                <item name="carriers/fedex/account" xsi:type="string">1</item>
+                <item name="carriers/fedex/key" xsi:type="string">1</item>
+                <item name="carriers/fedex/meter_number" xsi:type="string">1</item>
+                <item name="carriers/fedex/password" xsi:type="string">1</item>
+            </argument>
+        </arguments>
+    </type>
+</config>
\ No newline at end of file
diff --git a/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php b/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php
index 78946d236391e90eccec0d29def00f810db91eea..f4a2b1a81d7feeead8c6365f5291b6dd9466f02a 100644
--- a/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php
@@ -3,9 +3,6 @@
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
-// @codingStandardsIgnoreFile
-
 namespace Magento\Payment\Test\Unit\Block\Info;
 
 /**
@@ -31,71 +28,23 @@ class SubstitutionTest extends \PHPUnit_Framework_TestCase
     protected function setUp()
     {
         $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
-
-        $this->layout = $this->getMockBuilder(
-            \Magento\Framework\View\LayoutInterface::class
-        )->disableOriginalConstructor()->setMethods(
-            []
-        )->getMock();
-
-        $eventManager = $this->getMockBuilder(
-            \Magento\Framework\Event\ManagerInterface::class
-        )->disableOriginalConstructor()->setMethods(
-            []
-        )->getMock();
-
-        $scopeConfig = $this->getMockBuilder(
-            \Magento\Framework\App\Config\ScopeConfigInterface::class
-        )->disableOriginalConstructor()->setMethods(
-            []
-        )->getMock();
-        $scopeConfig->expects(
-            $this->any()
-        )->method(
-            'getValue'
-        )->with(
-            $this->stringContains(
-                'advanced/modules_disable_output/'
-            ),
-            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-        )->will(
-            $this->returnValue(
-                false
-            )
-        );
-
-        $context = $this->getMockBuilder(
-            \Magento\Framework\View\Element\Template\Context::class
-        )->disableOriginalConstructor()->setMethods(
-            ['getLayout', 'getEventManager', 'getScopeConfig']
-        )->getMock();
-        $context->expects(
-            $this->any()
-        )->method(
-            'getLayout'
-        )->will(
-            $this->returnValue(
-                $this->layout
-            )
-        );
-        $context->expects(
-            $this->any()
-        )->method(
-            'getEventManager'
-        )->will(
-            $this->returnValue(
-                $eventManager
-            )
-        );
-        $context->expects(
-            $this->any()
-        )->method(
-            'getScopeConfig'
-        )->will(
-            $this->returnValue(
-                $scopeConfig
-            )
-        );
+        $this->layout = $this->getMockBuilder(\Magento\Framework\View\LayoutInterface::class)
+            ->disableOriginalConstructor()
+            ->setMethods([])
+            ->getMock();
+        $eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class)
+            ->disableOriginalConstructor()
+            ->setMethods([])
+            ->getMock();
+        $context = $this->getMockBuilder(\Magento\Framework\View\Element\Template\Context::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getLayout', 'getEventManager', 'getScopeConfig'])
+            ->getMock();
+        $context->expects($this->any())
+            ->method('getLayout')
+            ->willReturn($this->layout);
+        $context->expects($this->any())->method('getEventManager')
+            ->willReturn($eventManager);
 
         $this->block = $this->objectManager->getObject(
             \Magento\Payment\Block\Info\Substitution::class,
@@ -110,52 +59,45 @@ class SubstitutionTest extends \PHPUnit_Framework_TestCase
 
     public function testBeforeToHtml()
     {
-        $abstractBlock = $this->getMockBuilder(
-            \Magento\Framework\View\Element\AbstractBlock::class
-        )->disableOriginalConstructor()->setMethods(
-            []
-        )->getMock();
-        $childAbstractBlock = clone($abstractBlock);
-
-        $abstractBlock->expects($this->any())->method('getParentBlock')->will($this->returnValue($childAbstractBlock));
+        $abstractBlock = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class)
+            ->disableOriginalConstructor()
+            ->setMethods([])
+            ->getMock();
+        $childAbstractBlock = clone $abstractBlock;
+
+        $abstractBlock->expects($this->any())
+            ->method('getParentBlock')
+            ->willReturn($childAbstractBlock);
+        $this->layout->expects($this->any())
+            ->method('getParentName')
+            ->willReturn('parentName');
+        $this->layout->expects($this->any())
+            ->method('getBlock')
+            ->willReturn($abstractBlock);
+
+        $infoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $methodMock = $this->getMockBuilder(\Magento\Payment\Model\MethodInterface::class)
+            ->getMockForAbstractClass();
+        $infoMock->expects($this->once())
+            ->method('getMethodInstance')
+            ->willReturn($methodMock);
 
-        $this->layout->expects($this->any())->method('getParentName')->will($this->returnValue('parentName'));
-        $this->layout->expects($this->any())->method('getBlock')->will($this->returnValue($abstractBlock));
-
-        $infoMock = $this->getMockBuilder(
-            \Magento\Payment\Model\Info::class
-        )->disableOriginalConstructor()->setMethods(
-            []
-        )->getMock();
-        $methodMock = $this->getMockBuilder(
-            \Magento\Payment\Model\MethodInterface::class
-        )->getMockForAbstractClass();
-        $infoMock->expects($this->once())->method('getMethodInstance')->will($this->returnValue($methodMock));
         $this->block->setInfo($infoMock);
 
         $fakeBlock = new \StdClass();
-        $this->layout->expects(
-            $this->any()
-        )->method(
-            'createBlock'
-        )->with(
-            \Magento\Framework\View\Element\Template::class,
-            '',
-            ['data' => ['method' => $methodMock, 'template' => 'Magento_Payment::info/substitution.phtml']]
-        )->will(
-                $this->returnValue(
-                    $fakeBlock
-                )
-            );
-
-        $childAbstractBlock->expects(
-            $this->any()
-        )->method(
-            'setChild'
-        )->with(
-            'order_payment_additional',
-            $fakeBlock
-        );
+        $this->layout->expects($this->any())
+            ->method('createBlock')
+            ->with(
+                \Magento\Framework\View\Element\Template::class,
+                '',
+                ['data' => ['method' => $methodMock, 'template' => 'Magento_Payment::info/substitution.phtml']]
+            )->willReturn($fakeBlock);
+
+        $childAbstractBlock->expects($this->any())
+            ->method('setChild')
+            ->with('order_payment_additional', $fakeBlock);
 
         $this->block->toHtml();
     }
diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php
index f478ea085e47a378573fc945a1afdf77b6eb942a..64f278c8e9f118b6b7e564da0c6de434052d4184 100644
--- a/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php
@@ -243,10 +243,6 @@ class AgreementsTest extends \PHPUnit_Framework_TestCase
             ->expects($this->at(1))
             ->method('dispatch')
             ->with('view_block_abstract_to_html_after', ['block' => $this->block, 'transport' => $transport]);
-        $this->scopeConfig
-            ->expects($this->once())
-            ->method('getValue')
-            ->willReturn(false);
         $this->urlBuilder->expects($this->once())->method('getUrl')->with('paypal/billing_agreement/startWizard', []);
         $this->block->toHtml();
     }
diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php
index cf16349e5873468cef013f42601895a28244cba5..17468c02578693be75efd4af8bbef3f73d868871 100644
--- a/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php
@@ -3,11 +3,9 @@
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
 namespace Magento\Paypal\Test\Unit\Block\Express;
 
 use Magento\Paypal\Block\Express\Review;
-use Magento\Quote\Model\Quote\Address\Rate;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -35,14 +33,6 @@ class ReviewTest extends \PHPUnit_Framework_TestCase
 
         $layout = $this->getMock(\Magento\Framework\View\LayoutInterface::class, [], [], '', false);
         $eventManager = $this->getMock(\Magento\Framework\Event\ManagerInterface::class, [], [], '', false);
-        $scopeConfig = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class, [], [], '', false);
-
-        $scopeConfig->expects($this->any())
-            ->method('getValue')
-            ->with(
-                $this->stringContains('advanced/modules_disable_output/'),
-                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-            )->will($this->returnValue(false));
 
         $urlBuilder = $this->getMock(\Magento\Framework\UrlInterface::class);
         $urlBuilder->expects($this->any())->method('getUrl')->will($this->returnArgument(0));
@@ -60,7 +50,6 @@ class ReviewTest extends \PHPUnit_Framework_TestCase
 
         $context->expects($this->any())->method('getLayout')->will($this->returnValue($layout));
         $context->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager));
-        $context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($scopeConfig));
         $context->expects($this->any())->method('getRequest')->will($this->returnValue($this->request));
         $context->expects($this->any())->method('getAssetRepository')->will($this->returnValue($this->assetRepo));
         $context->expects($this->any())->method('getUrlBuilder')->will($this->returnValue($urlBuilder));
diff --git a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php
index 44b494f2a41df8c7394c83396387d577bbb5e641..f80c1018d322c35bd8dd823e2c6e1d0d58fca96b 100644
--- a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php
+++ b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php
@@ -149,8 +149,7 @@ class AdditionalTest extends \PHPUnit_Framework_TestCase
             '',
             false,
             true,
-            true,
-            ['getValue']
+            true
         );
         $this->cacheStateMock = $this->getMockForAbstractClass(
             \Magento\Framework\App\Cache\StateInterface::class,
@@ -267,12 +266,6 @@ class AdditionalTest extends \PHPUnit_Framework_TestCase
         $this->eventManagerMock->expects($this->at(1))
             ->method('dispatch')
             ->with('view_block_abstract_to_html_after');
-        $this->scopeConfigMock->expects($this->once())
-            ->method('getValue')
-            ->with(
-                'advanced/modules_disable_output/Magento_Persistent',
-                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-            )->willReturn(false);
 
         // get cache
         $this->cacheStateMock->expects($this->at(0))
diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php
index 6f63d94fd00ba7bc21de39313e3aa3809fd075d4..a3186aef6247d4729de611a73910d566f71e0326 100644
--- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php
+++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php
@@ -395,7 +395,10 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject
 
         if ($putOrderId) {
             $page->drawText(__('Order # ') . $order->getRealOrderId(), 35, $top -= 30, 'UTF-8');
+            $top +=15;
         }
+
+        $top -=30;
         $page->drawText(
             __('Order Date: ') .
             $this->_localeDate->formatDate(
@@ -408,7 +411,7 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject
                 false
             ),
             35,
-            $top -= 15,
+            $top,
             'UTF-8'
         );
 
diff --git a/app/code/Magento/SampleData/etc/module.xml b/app/code/Magento/SampleData/etc/module.xml
index 24be4417e689facd4548df8c6e6e6804bc8f7700..e511dfd4bbb765c5e0031bea552dbdc354bb8dc3 100644
--- a/app/code/Magento/SampleData/etc/module.xml
+++ b/app/code/Magento/SampleData/etc/module.xml
@@ -7,5 +7,8 @@
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
     <module name="Magento_SampleData" setup_version="2.0.0">
+        <sequence>
+            <module name="Magento_UrlRewrite"/>
+        </sequence>
     </module>
 </config>
diff --git a/app/code/Magento/Store/Model/Scope/Validator.php b/app/code/Magento/Store/Model/Scope/Validator.php
new file mode 100644
index 0000000000000000000000000000000000000000..f2222fdc476a9a61ee9ed37c285332561f6dd43b
--- /dev/null
+++ b/app/code/Magento/Store/Model/Scope/Validator.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Store\Model\Scope;
+
+use InvalidArgumentException;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Scope\ValidatorInterface;
+use Magento\Framework\App\ScopeResolverPool;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
+
+/**
+ * Class Validator validates scope and scope code.
+ */
+class Validator implements ValidatorInterface
+{
+    /**
+     * @var ScopeResolverPool
+     */
+    private $scopeResolverPool;
+
+    /**
+     * @param ScopeResolverPool $scopeResolverPool
+     */
+    public function __construct(ScopeResolverPool $scopeResolverPool)
+    {
+        $this->scopeResolverPool = $scopeResolverPool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isValid($scope, $scopeCode = null)
+    {
+        if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT && empty($scopeCode)) {
+            return true;
+        }
+
+        if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT && !empty($scopeCode)) {
+            throw new LocalizedException(__(
+                'The "%1" scope can\'t include a scope code. Try again without entering a scope code.',
+                ScopeConfigInterface::SCOPE_TYPE_DEFAULT
+            ));
+        }
+
+        if (empty($scope)) {
+            throw new LocalizedException(__('Enter a scope before proceeding.'));
+        }
+
+        $this->validateScopeCode($scopeCode);
+
+        try {
+            $scopeResolver = $this->scopeResolverPool->get($scope);
+            $scopeResolver->getScope($scopeCode)->getId();
+        } catch (InvalidArgumentException $e) {
+            throw new LocalizedException(__('The "%1" value doesn\'t exist. Enter another value.', $scope));
+        } catch (NoSuchEntityException $e) {
+            throw new LocalizedException(__('The "%1" value doesn\'t exist. Enter another value.', $scopeCode));
+        }
+
+        return true;
+    }
+
+    /**
+     * Validate scope code
+     * Throw exception if not valid.
+     *
+     * @param string $scopeCode
+     * @return void
+     * @throws LocalizedException if scope code is empty or has a wrong format
+     */
+    private function validateScopeCode($scopeCode)
+    {
+        if (empty($scopeCode)) {
+            throw new LocalizedException(__('Enter a scope code before proceeding.'));
+        }
+
+        if (!preg_match('/^[a-z]+[a-z0-9_]*$/', $scopeCode)) {
+            throw new LocalizedException(__(
+                'The scope code can include only lowercase letters (a-z), numbers (0-9) and underscores (_). '
+                . 'Also, the first character must be a letter.'
+            ));
+        }
+    }
+}
diff --git a/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php b/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..51c58739f5ff5c10f19ebcbef9d7a92df33611b0
--- /dev/null
+++ b/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Store\Test\Unit\Model\Scope;
+
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ScopeInterface;
+use Magento\Framework\App\ScopeResolverInterface;
+use Magento\Framework\App\ScopeResolverPool;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Store\Model\Scope\Validator;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+class ValidatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Validator
+     */
+    private $model;
+
+    /**
+     * @var ScopeResolverPool|MockObject
+     */
+    private $scopeResolverPoolMock;
+
+    protected function setUp()
+    {
+        $this->scopeResolverPoolMock = $this->getMockBuilder(ScopeResolverPool::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->model = new Validator(
+            $this->scopeResolverPoolMock
+        );
+    }
+
+    public function testIsValid()
+    {
+        $scope = 'not_default_scope';
+        $scopeCode = 'not_exist_scope_code';
+
+        $scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class)
+            ->getMockForAbstractClass();
+        $scopeObject = $this->getMockBuilder(ScopeInterface::class)
+            ->getMockForAbstractClass();
+        $scopeResolver->expects($this->once())
+            ->method('getScope')
+            ->with($scopeCode)
+            ->willReturn($scopeObject);
+        $this->scopeResolverPoolMock->expects($this->once())
+            ->method('get')
+            ->with($scope)
+            ->willReturn($scopeResolver);
+
+        $this->assertTrue($this->model->isValid($scope, $scopeCode));
+    }
+
+    public function testIsValidDefault()
+    {
+        $this->assertTrue($this->model->isValid(ScopeConfigInterface::SCOPE_TYPE_DEFAULT));
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage The "default" scope can't include a scope code. Try again without entering a scope
+     */
+    public function testNotEmptyScopeCodeForDefaultScope()
+    {
+        $this->model->isValid(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 'some_code');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Enter a scope before proceeding.
+     */
+    public function testEmptyScope()
+    {
+        $this->model->isValid('', 'some_code');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Enter a scope code before proceeding.
+     */
+    public function testEmptyScopeCode()
+    {
+        $this->model->isValid('not_default_scope', '');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage The scope code can include only lowercase letters (a-z), numbers (0-9) and underscores
+     */
+    public function testWrongScopeCodeFormat()
+    {
+        $this->model->isValid('not_default_scope', '123');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage The "not_default_scope" value doesn't exist. Enter another value.
+     */
+    public function testScopeNotExist()
+    {
+        $scope = 'not_default_scope';
+        $this->scopeResolverPoolMock->expects($this->once())
+            ->method('get')
+            ->with($scope)
+            ->willThrowException(new \InvalidArgumentException());
+
+        $this->model->isValid($scope, 'scope_code');
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage The "not_exist_scope_code" value doesn't exist. Enter another value.
+     */
+    public function testScopeCodeNotExist()
+    {
+        $scope = 'not_default_scope';
+        $scopeCode = 'not_exist_scope_code';
+
+        $scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class)
+            ->getMockForAbstractClass();
+        $scopeResolver->expects($this->once())
+            ->method('getScope')
+            ->with($scopeCode)
+            ->willThrowException(new NoSuchEntityException());
+        $this->scopeResolverPoolMock->expects($this->once())
+            ->method('get')
+            ->with($scope)
+            ->willReturn($scopeResolver);
+
+        $this->model->isValid($scope, $scopeCode);
+    }
+}
diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml
index 48a37dec7d73a9ae9fda07cce9c709b3d0b94284..d3b91a57ef3141fb054239ebd919cb48d77ad5e8 100644
--- a/app/code/Magento/Store/etc/di.xml
+++ b/app/code/Magento/Store/etc/di.xml
@@ -25,6 +25,7 @@
     <preference for="Magento\Framework\App\ScopeFallbackResolverInterface" type="Magento\Store\Model\ScopeFallbackResolver"/>
     <preference for="Magento\Framework\App\ScopeTreeProviderInterface" type="Magento\Store\Model\ScopeTreeProvider"/>
     <preference for="Magento\Framework\App\ScopeValidatorInterface" type="Magento\Store\Model\ScopeValidator"/>
+    <preference for="Magento\Framework\App\Scope\ValidatorInterface" type="Magento\Store\Model\Scope\Validator"/>
     <type name="Magento\Framework\App\Response\Http">
         <plugin name="genericHeaderPlugin" type="Magento\Framework\App\Response\HeaderManager"/>
     </type>
diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php
index 5b2698749019e6c983804ac12d14513e5cfc03d0..8181f3c6d80a97859e2a952920029bd7885a9525 100644
--- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php
+++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php
@@ -6,8 +6,6 @@
  */
 namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite;
 
-use Magento\Catalog\Model\Category;
-use Magento\Catalog\Model\Product;
 use Magento\Framework\Exception\LocalizedException;
 use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
@@ -63,7 +61,7 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite
                 $model->setEntityType($productId ? self::ENTITY_TYPE_PRODUCT : self::ENTITY_TYPE_CATEGORY)
                     ->setEntityId($productId ?: $categoryId);
                 if ($productId && $categoryId) {
-                    $model->setMetadata(serialize(['category_id' => $categoryId]));
+                    $model->setMetadata(['category_id' => $categoryId]);
                 }
             }
             $model->setTargetPath($this->getTargetPath($model));
diff --git a/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..20ecf0f52c05781d8acaf076cca745e04481347e
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Model;
+
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteService;
+
+/**
+ * This class is to be used as a container for new generated url rewrites by adding new ones using merge method
+ * Removes duplicates for a set/array of Url Rewrites based on the unique key of the url_rewrites table
+ *
+ */
+class MergeDataProvider
+{
+    const SEPARATOR = '_';
+
+    /**
+     * @var $rewritesArray[]
+     */
+    private $data = [];
+
+    /**
+     * Adds url rewrites to class data container by removing duplicates by a unique key
+     *
+     * @param UrlRewriteService[] $urlRewritesArray
+     * @return void
+     */
+    public function merge(array $urlRewritesArray)
+    {
+        foreach ($urlRewritesArray as $urlRewrite) {
+            $key = $urlRewrite->getRequestPath() . self::SEPARATOR . $urlRewrite->getStoreId();
+            if ($key !== self::SEPARATOR) {
+                $this->data[$key] = $urlRewrite;
+            } else {
+                $this->data[] = $urlRewrite;
+            }
+        }
+    }
+
+    /**
+     * Returns the data added to container
+     *
+     * @return UrlRewriteService[]
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/Model/UrlRewrite.php b/app/code/Magento/UrlRewrite/Model/UrlRewrite.php
index b8f97ace72936221e2626d3107d89246158f1cb2..e2eaae257b2912c6cfa0178739c70a949d0c06e2 100644
--- a/app/code/Magento/UrlRewrite/Model/UrlRewrite.php
+++ b/app/code/Magento/UrlRewrite/Model/UrlRewrite.php
@@ -5,6 +5,9 @@
  */
 namespace Magento\UrlRewrite\Model;
 
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
+
 /**
  * @method int getEntityId()
  * @method string getEntityType()
@@ -14,7 +17,6 @@ namespace Magento\UrlRewrite\Model;
  * @method string getTargetPath()
  * @method UrlRewrite setEntityId(int $value)
  * @method UrlRewrite setEntityType(string $value)
- * @method UrlRewrite setMetadata($value)
  * @method UrlRewrite setRequestPath($value)
  * @method UrlRewrite setTargetPath($value)
  * @method UrlRewrite setRedirectType($value)
@@ -23,6 +25,32 @@ namespace Magento\UrlRewrite\Model;
  */
 class UrlRewrite extends \Magento\Framework\Model\AbstractModel
 {
+    /**
+     * @var Json
+     */
+    private $serializer;
+
+    /**
+     * UrlRewrite constructor.
+     * @param \Magento\Framework\Model\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
+     * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
+     * @param array $data
+     * @param Json $serializer
+     */
+    public function __construct(
+        \Magento\Framework\Model\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
+        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
+        array $data = [],
+        Json $serializer = null
+    ) {
+        $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
+        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
+    }
+
     /**
      * Initialize corresponding resource model
      *
@@ -41,6 +69,21 @@ class UrlRewrite extends \Magento\Framework\Model\AbstractModel
     public function getMetadata()
     {
         $metadata = $this->getData(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA);
-        return !empty($metadata) ? unserialize($metadata) : [];
+        return !empty($metadata) ? $this->serializer->unserialize($metadata) : [];
+    }
+
+    /**
+     * Overwrite Metadata in the object.
+     *
+     * @param array|string $metadata
+     *
+     * @return $this
+     */
+    public function setMetadata($metadata)
+    {
+        if (is_array($metadata)) {
+            $metadata = $this->serializer->serialize($metadata);
+        }
+        return $this->setData(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA, $metadata);
     }
 }
diff --git a/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php b/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php
index 11315279ff219b1a461bfe6aa447a2092cbb3f84..8ed0479a07637fac5cc6f95dfd510c6240e0ad96 100644
--- a/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php
+++ b/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php
@@ -6,6 +6,8 @@
 namespace Magento\UrlRewrite\Service\V1\Data;
 
 use Magento\Framework\Api\AbstractSimpleObject;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
 
 /**
  * Data abstract class for url storage
@@ -37,6 +39,25 @@ class UrlRewrite extends AbstractSimpleObject
         self::DESCRIPTION => null,
     ];
 
+    /**
+     * @var Json
+     */
+    private $serializer;
+
+    /**
+     * UrlRewrite constructor.
+     *
+     * @param array $data
+     * @param Json $serializer
+     */
+    public function __construct(
+        $data = [],
+        Json $serializer = null
+    ) {
+        $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
+        parent::__construct($data);
+    }
+
     /**
      * Get data by key
      *
@@ -216,7 +237,7 @@ class UrlRewrite extends AbstractSimpleObject
     public function getMetadata()
     {
         $metadata = $this->_get(self::METADATA);
-        return !empty($metadata) ? unserialize($metadata) : [];
+        return !empty($metadata) ? $this->serializer->unserialize($metadata) : [];
     }
 
     /**
@@ -227,7 +248,7 @@ class UrlRewrite extends AbstractSimpleObject
     public function setMetadata($metadata)
     {
         if (is_array($metadata)) {
-            $metadata = serialize($metadata);
+            $metadata = $this->serializer->serialize($metadata);
         }
         return $this->setData(UrlRewrite::METADATA, $metadata);
     }
diff --git a/app/code/Magento/UrlRewrite/Setup/UpgradeData.php b/app/code/Magento/UrlRewrite/Setup/UpgradeData.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad71639398e1b71a83dcb8c869739f2a40079945
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Setup/UpgradeData.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Setup;
+
+use Magento\Framework\DB\FieldDataConverterFactory;
+use Magento\Framework\DB\DataConverter\SerializedToJson;
+use Magento\Framework\Setup\ModuleContextInterface;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Framework\Setup\UpgradeDataInterface;
+
+class UpgradeData implements UpgradeDataInterface
+{
+    /**
+     * @var FieldDataConverterFactory
+     */
+    private $fieldDataConverterFactory;
+
+    /**
+     * Constructor
+     *
+     * @param FieldDataConverterFactory $fieldDataConverterFactory
+     */
+    public function __construct(
+        FieldDataConverterFactory $fieldDataConverterFactory
+    ) {
+        $this->fieldDataConverterFactory = $fieldDataConverterFactory;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
+    {
+        $setup->startSetup();
+
+        if (version_compare($context->getVersion(), '2.0.1', '<')) {
+            $this->convertSerializedDataToJson($setup);
+        }
+
+        $setup->endSetup();
+    }
+
+    /**
+     * Convert metadata from serialized to JSON format:
+     *
+     * @param ModuleDataSetupInterface $setup
+     *
+     * @return void
+     */
+    public function convertSerializedDataToJson($setup)
+    {
+        $fieldDataConverter = $this->fieldDataConverterFactory->create(SerializedToJson::class);
+        $fieldDataConverter->convert(
+            $setup->getConnection(),
+            $setup->getTable('url_rewrite'),
+            'url_rewrite_id',
+            'metadata'
+        );
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php
index 9cf18a76cd045c8abd75d9f81c533d045df5f8ff..5118d12fad6c6e6b069a5a080c412ec1fd026e88 100644
--- a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php
+++ b/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php
@@ -81,7 +81,7 @@ class SetRedirectUrlTest extends \PHPUnit_Framework_TestCase
 
     public function testGetTargetStorePostData()
     {
-        $urlRewrite = $this->getMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class);
+        $urlRewrite = $this->getMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class, [], [], '', false);
         $urlRewrite->expects($this->once())->method('getRequestPath')->willReturn('path');
 
         $this->request->expects($this->once())->method('getPathInfo')->willReturn('path');
diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..76aa5d368be4c805155b374245799197e4b538dd
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Test\Unit\Model;
+
+use Magento\UrlRewrite\Model\MergeDataProvider;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
+class MergeDataProviderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $urlRewritesSet;
+
+    /**
+     * Set up
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->urlRewritesSet = (new ObjectManager($this))->getObject(
+            MergeDataProvider::class,
+            []
+        );
+    }
+
+    /**
+     * Run test merge method
+     *
+     * @param array $urlRewriteMockArray
+     * @param String $expectedData
+     * @param int $arrayCount
+     * @dataProvider mergeDataProvider
+     * @return void
+     */
+    public function testMerge($urlRewriteMockArray, $expectedData, $arrayCount)
+    {
+        $this->urlRewritesSet->merge($urlRewriteMockArray);
+        $this->assertEquals($expectedData, $this->urlRewritesSet->getData());
+        $this->assertCount($arrayCount, $this->urlRewritesSet->getData());
+    }
+
+    /**
+     * Run test getData method when data is Empty
+     *
+     * @return void
+     */
+    public function testGetDataWhenEmpty()
+    {
+        $this->assertEmpty($this->urlRewritesSet->getData());
+    }
+
+    /**
+     * Data provider for testMerge
+     *
+     * @return array
+     */
+    public function mergeDataProvider()
+    {
+        $urlRewriteMock1 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $requestPathForMock2 = 'magento.tst/products/simpleproduct2';
+        $storeIdForMock2 = 'testStore2';
+        $urlRewriteMock2 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $urlRewriteMock2->expects($this->atLeastOnce())
+            ->method('getRequestPath')
+            ->willReturn($requestPathForMock2);
+
+        $urlRewriteMock2->expects($this->atLeastOnce())
+            ->method('getStoreId')
+            ->willReturn($storeIdForMock2);
+
+        $requestPathForMock3 = 'magento.tst/products/simpleproduct3';
+        $storeIdForMock3 = 'testStore3';
+        $urlRewriteMock3 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $urlRewriteMock3->expects($this->atLeastOnce())
+            ->method('getRequestPath')
+            ->willReturn($requestPathForMock3);
+
+        $urlRewriteMock3->expects($this->atLeastOnce())
+            ->method('getStoreId')
+            ->willReturn($storeIdForMock3);
+
+        return [
+            [
+                [],
+                [],
+                0
+            ],
+            [
+                [$urlRewriteMock1],
+                [$urlRewriteMock1],
+                1
+            ],
+            [
+                [
+                    $urlRewriteMock1,
+                    $urlRewriteMock2,
+                    $urlRewriteMock2
+                ],
+                [
+                    $urlRewriteMock1,
+                    $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2
+                ],
+                2
+            ],
+            [
+                [
+                    $urlRewriteMock1,
+                    $urlRewriteMock2,
+                    $urlRewriteMock3
+                ],
+                [
+                    $urlRewriteMock1,
+                    $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2,
+                    $requestPathForMock3 . '_' . $storeIdForMock3 => $urlRewriteMock3
+                ],
+                3
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..db536c5aa38b3622db9c5cc2d9a4d07348e7fae6
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Test\Unit\Model;
+
+class UrlRewriteTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\UrlRewrite\Model\UrlRewrite
+     */
+    protected $model;
+
+    protected function setUp()
+    {
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $context = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false);
+        $registry = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false);
+        $resource = $this->getMock(
+            \Magento\Framework\Model\ResourceModel\AbstractResource::class,
+            ['getIdFieldName', '_construct', 'getConnection'],
+            [],
+            '',
+            false
+        );
+        $resourceCollection = $this->getMock(\Magento\Framework\Data\Collection\AbstractDb::class, [], [], '', false);
+        $serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false);
+        $serializer->expects($this->any())
+            ->method('serialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_encode($value);
+                }
+            );
+        $serializer->expects($this->any())
+            ->method('unserialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_decode($value, true);
+                }
+            );
+
+        $this->model = $objectManager->getObject(
+            \Magento\UrlRewrite\Model\UrlRewrite::class,
+            [
+                'context' => $context,
+                'registry' => $registry,
+                'resource' => $resource,
+                'resourceCollection' => $resourceCollection,
+                'data' => [],
+                'serializer' => $serializer,
+            ]
+        );
+    }
+
+    public function testSetAndGetMetadata()
+    {
+        $testData = [1, 2, 3];
+
+        $this->model->setMetadata($testData);
+
+        $this->assertEquals($testData, $this->model->getMetadata());
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bce4505b2eda2ecbc09c5e7b33173c2153703bb2
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Test\Unit\Service\V1\Data;
+
+class UrlRewriteTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\UrlRewrite\Model\UrlRewrite
+     */
+    protected $model;
+
+    protected function setUp()
+    {
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false);
+        $serializer->expects($this->any())
+            ->method('serialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_encode($value);
+                }
+            );
+        $serializer->expects($this->any())
+            ->method('unserialize')
+            ->willReturnCallback(
+                function ($value) {
+                    return json_decode($value, true);
+                }
+            );
+
+        $this->model = $objectManager->getObject(
+            \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class,
+            [
+                'data' => [],
+                'serializer' => $serializer,
+            ]
+        );
+    }
+
+    public function testSetAndGetMetadata()
+    {
+        $testData = [1, 2, 3];
+
+        $this->model->setMetadata($testData);
+
+        $this->assertEquals($testData, $this->model->getMetadata());
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/etc/module.xml b/app/code/Magento/UrlRewrite/etc/module.xml
index 284c67950ac539e6d4f030c32611981278f51c14..499a5166a860691e10fb7214b5a8983dd0d0ea3a 100644
--- a/app/code/Magento/UrlRewrite/etc/module.xml
+++ b/app/code/Magento/UrlRewrite/etc/module.xml
@@ -6,6 +6,6 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
-    <module name="Magento_UrlRewrite" setup_version="2.0.0">
+    <module name="Magento_UrlRewrite" setup_version="2.0.1">
     </module>
 </config>
diff --git a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php
index 837b7f7135d29d22580aaf1103b9a5c3ae2579d2..c23a8019e37eeb3ebebd8662aea23a773dff036e 100644
--- a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php
+++ b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php
@@ -67,7 +67,6 @@ class ContainerTest extends AbstractContainerTest
             . '<option value="content.top" >Main Content Top</option></select>';
 
         $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true);
-        $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false);
 
         $this->themeCollectionFactoryMock->expects($this->once())
             ->method('create')
@@ -154,7 +153,6 @@ class ContainerTest extends AbstractContainerTest
             . '<option value="sidebar.main" >Sidebar Main</option></select>';
 
         $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true);
-        $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false);
 
         $this->themeCollectionFactoryMock->expects($this->once())
             ->method('create')
@@ -285,7 +283,6 @@ class ContainerTest extends AbstractContainerTest
             . '<option value="sidebar.main" >Sidebar Main</option></select>';
 
         $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true);
-        $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false);
 
         $this->themeCollectionFactoryMock->expects($this->once())
             ->method('create')
@@ -403,7 +400,6 @@ class ContainerTest extends AbstractContainerTest
             . 'Sidebar Main</option></select>';
 
         $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true);
-        $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false);
 
         $this->themeCollectionFactoryMock->expects($this->once())
             ->method('create')
diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php
index b9dcc8d6fe73f500b1d314b90bf992c672fe14cf..e9c22670b1a104a46aa30525c70593ee40bdf81f 100644
--- a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php
+++ b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php
@@ -136,7 +136,6 @@ class WishlistTest extends \PHPUnit_Framework_TestCase
         );
         $customerServiceMock = $this->getMock(\Magento\Customer\Api\Data\CustomerInterface::class, [], [], '', false);
         $wishlistSharingUrl = 'wishlist/shared/index/1';
-        $locale = 'en_US';
         $productUrl = 'http://product.url/';
         $productName = 'Product name';
 
@@ -160,25 +159,6 @@ class WishlistTest extends \PHPUnit_Framework_TestCase
         $this->urlBuilderMock->expects($this->once())
             ->method('getUrl')
             ->will($this->returnValue($wishlistSharingUrl));
-        $this->scopeConfig->expects($this->any())
-            ->method('getValue')
-            ->will($this->returnValueMap(
-                    [
-                        [
-                            'advanced/modules_disable_output/Magento_Rss',
-                            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
-                            null,
-                            null,
-                        ],
-                        [
-                            Data::XML_PATH_DEFAULT_LOCALE,
-                            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
-                            null,
-                            $locale
-                        ],
-                    ]
-                )
-            );
 
         $staticArgs = [
             'productName' => $productName,
diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
index eb7f5781976adeead4d2768c71f1eb52f7017e59..a9028fa65cebf704facd24caa5f8ba9b81956c4a 100644
--- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
@@ -492,15 +492,23 @@
     .block-addresses-list {
         .items.addresses {
             &:extend(.abs-add-clearfix-desktop all);
+            font-size: 0;
 
             > .item {
-                &:extend(.abs-blocks-2columns all);
+                display: inline-block;
+                font-size: @font-size__base;
                 margin-bottom: @indent__base;
+                vertical-align: top;
+                width: 48.8%;
 
                 &:nth-last-child(1),
                 &:nth-last-child(2) {
                     margin-bottom: 0;
                 }
+
+                &:nth-child(even) {
+                    margin-left: 2.4%;
+                }
             }
         }
     }
diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
index 74c7b8d3f2970dac9edea2e8c2c4a98e798e83f2..a9b42a82036bc70abf4eefb883c187e5417ff198 100644
--- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
@@ -106,6 +106,8 @@
 
     .box-billing-address,
     .box-shipping-address,
+    .box-address-billing,
+    .box-address-shipping,
     .box-information,
     .box-newsletter {
         .box-content {
@@ -437,15 +439,23 @@
     .block-addresses-list {
         .items.addresses {
             &:extend(.abs-add-clearfix-desktop all);
+            font-size: 0;
 
             > .item {
-                &:extend(.abs-blocks-2columns all);
+                display: inline-block;
+                font-size: @font-size__base;
                 margin-bottom: @indent__base;
+                vertical-align: top;
+                width: 48%;
 
                 &:nth-last-child(1),
                 &:nth-last-child(2) {
                     margin-bottom: 0;
                 }
+
+                &:nth-child(even) {
+                    margin-left: 4%;
+                }
             }
         }
 
@@ -479,6 +489,8 @@
 
     .box-billing-address,
     .box-shipping-address,
+    .box-address-billing,
+    .box-address-shipping,
     .box-information {
         .box-content {
             &:extend(.abs-account-block-font-size all);
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 3a5533fd825ce677d5df3f6ff2dded69af010693..a7c38ae1246b602a105c5e1a588fc5e7b9561b2e 100755
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -1221,4 +1221,17 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Framework\DB\TemporaryTableService">
+        <arguments>
+            <argument name="allowedIndexMethods" xsi:type="array">
+                <item name="HASH" xsi:type="string">HASH</item>
+                <item name="BTREE" xsi:type="string">BTREE</item>
+            </argument>
+            <argument name="allowedEngines" xsi:type="array">
+                <item name="INNODB" xsi:type="string">INNODB</item>
+                <item name="MEMORY" xsi:type="string">MEMORY</item>
+                <item name="MYISAM" xsi:type="string">MYISAM</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/bin/magento b/bin/magento
index 1cb700def7fa7f3faf073dbd7bb0300892b06332..3fa9221763cf5fca0a09eb4bc6f43d3a5cb22a42 100755
--- a/bin/magento
+++ b/bin/magento
@@ -28,5 +28,5 @@ try {
         echo "\n\n";
         $e = $e->getPrevious();
     }
-    exit(Cli::RETURN_FAILURE);
+    exit(Magento\Framework\Console\Cli::RETURN_FAILURE);
 }
diff --git a/composer.json b/composer.json
index 11fe00b7ed45add98a8996bea8a480403d44a53c..16c73d23ef6d8a743ed69cfa53e890e8e2642a7f 100644
--- a/composer.json
+++ b/composer.json
@@ -226,7 +226,7 @@
         "psr-0": {
             "": [
                 "app/code/",
-                "var/generation"
+                "generated/code"
             ]
         },
         "files": [
diff --git a/composer.lock b/composer.lock
index e69e12953fea2c150e1e063ddb9502148aad38ec..43c927cb95a1d5eb07b272dda0b018be12c73fbe 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "01335ef9c76fd6d77bf6bd9236b84d03",
+    "hash": "e5946b0500f46fab5f05e46957258ed4",
     "content-hash": "07be52ec4880d0390acff2dd605a194d",
     "packages": [
         {
@@ -554,16 +554,16 @@
         },
         {
             "name": "magento/magento-composer-installer",
-            "version": "0.1.11",
+            "version": "0.1.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/magento/magento-composer-installer.git",
-                "reference": "a12b9577cd9859af67d2365ae38d23ddfc57cf4d"
+                "reference": "10c600e88ad34fec71bb6b435ea8415ce92d51de"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/a12b9577cd9859af67d2365ae38d23ddfc57cf4d",
-                "reference": "a12b9577cd9859af67d2365ae38d23ddfc57cf4d",
+                "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/10c600e88ad34fec71bb6b435ea8415ce92d51de",
+                "reference": "10c600e88ad34fec71bb6b435ea8415ce92d51de",
                 "shasum": ""
             },
             "require": {
@@ -629,7 +629,7 @@
                 "composer-installer",
                 "magento"
             ],
-            "time": "2016-06-15 04:02:25"
+            "time": "2016-10-06 16:05:07"
         },
         {
             "name": "magento/zendframework1",
@@ -1687,7 +1687,7 @@
         },
         {
             "name": "zendframework/zend-code",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-code.git",
@@ -1740,7 +1740,7 @@
         },
         {
             "name": "zendframework/zend-config",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-config.git",
@@ -1797,7 +1797,7 @@
         },
         {
             "name": "zendframework/zend-console",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-console.git",
@@ -1847,7 +1847,7 @@
         },
         {
             "name": "zendframework/zend-crypt",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-crypt.git",
@@ -1899,7 +1899,7 @@
         },
         {
             "name": "zendframework/zend-di",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-di.git",
@@ -1950,7 +1950,7 @@
         },
         {
             "name": "zendframework/zend-escaper",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-escaper.git",
@@ -1995,7 +1995,7 @@
         },
         {
             "name": "zendframework/zend-eventmanager",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-eventmanager.git",
@@ -2041,7 +2041,7 @@
         },
         {
             "name": "zendframework/zend-filter",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-filter.git",
@@ -2097,7 +2097,7 @@
         },
         {
             "name": "zendframework/zend-form",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-form.git",
@@ -2168,7 +2168,7 @@
         },
         {
             "name": "zendframework/zend-http",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-http.git",
@@ -2219,7 +2219,7 @@
         },
         {
             "name": "zendframework/zend-i18n",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-i18n.git",
@@ -2283,7 +2283,7 @@
         },
         {
             "name": "zendframework/zend-inputfilter",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-inputfilter.git",
@@ -2334,7 +2334,7 @@
         },
         {
             "name": "zendframework/zend-json",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-json.git",
@@ -2388,7 +2388,7 @@
         },
         {
             "name": "zendframework/zend-loader",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-loader.git",
@@ -2433,7 +2433,7 @@
         },
         {
             "name": "zendframework/zend-log",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-log.git",
@@ -2495,7 +2495,7 @@
         },
         {
             "name": "zendframework/zend-math",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-math.git",
@@ -2546,7 +2546,7 @@
         },
         {
             "name": "zendframework/zend-modulemanager",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-modulemanager.git",
@@ -2604,7 +2604,7 @@
         },
         {
             "name": "zendframework/zend-mvc",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-mvc.git",
@@ -2692,7 +2692,7 @@
         },
         {
             "name": "zendframework/zend-serializer",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-serializer.git",
@@ -2745,7 +2745,7 @@
         },
         {
             "name": "zendframework/zend-server",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-server.git",
@@ -2792,7 +2792,7 @@
         },
         {
             "name": "zendframework/zend-servicemanager",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-servicemanager.git",
@@ -2842,7 +2842,7 @@
         },
         {
             "name": "zendframework/zend-soap",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-soap.git",
@@ -2894,7 +2894,7 @@
         },
         {
             "name": "zendframework/zend-stdlib",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-stdlib.git",
@@ -2949,7 +2949,7 @@
         },
         {
             "name": "zendframework/zend-text",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-text.git",
@@ -2996,7 +2996,7 @@
         },
         {
             "name": "zendframework/zend-uri",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-uri.git",
@@ -3044,7 +3044,7 @@
         },
         {
             "name": "zendframework/zend-validator",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-validator.git",
@@ -3109,7 +3109,7 @@
         },
         {
             "name": "zendframework/zend-view",
-            "version": "2.4.10",
+            "version": "2.4.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/zendframework/zend-view.git",
diff --git a/dev/tests/functional/.htaccess.sample b/dev/tests/functional/.htaccess.sample
index f1e60cf9b67e91779153e4c9d9f780c8f7bc9438..c1fcc7c278c8aa0e79bd372c89ed6ef6e8ec2f0f 100644
--- a/dev/tests/functional/.htaccess.sample
+++ b/dev/tests/functional/.htaccess.sample
@@ -1,6 +1,6 @@
 ##############################################
 ## Allow access to command.php and website.php
-    <FilesMatch "command.php|website.php|export.php">
+    <FilesMatch "command.php|website.php|export.php|pathChecker.php|deleteMagentoGeneratedCode.php">
          order allow,deny
          allow from all
     </FilesMatch>
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php
index 385c11cd74f82e4adbc78f5e26b14e1e03931dae..5f5fe66d96cef6cc25689b315a31d91214e01719 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php
@@ -18,6 +18,11 @@ class Setup extends Cli
      */
     const PARAM_SETUP_UNINSTALL = 'setup:uninstall';
 
+    /**
+     * Parameter for DI compile Magento command.
+     */
+    const PARAM_SETUP_DI_COMPILE = 'setup:di:compile';
+
     /**
      * Options for uninstall Magento command.
      *
@@ -34,4 +39,14 @@ class Setup extends Cli
     {
         parent::execute(Setup::PARAM_SETUP_UNINSTALL, $this->options);
     }
+
+    /**
+     * DI Compile.
+     *
+     * @return void
+     */
+    public function diCompile()
+    {
+        parent::execute(Setup::PARAM_SETUP_DI_COMPILE);
+    }
 }
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php
new file mode 100644
index 0000000000000000000000000000000000000000..c82231a9e38f1a10dab7cc822bb4bed2f092cb2d
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Mtf\Util\Command;
+
+use Magento\Mtf\Util\Protocol\CurlInterface;
+use Magento\Mtf\Util\Protocol\CurlTransport;
+
+/**
+ * GeneratedCode removes generated code of Magento (like generated/code and generated/metadata).
+ */
+class GeneratedCode
+{
+    /**
+     * Url to deleteMagentoGeneratedCode.php.
+     */
+    const URL = 'dev/tests/functional/utils/deleteMagentoGeneratedCode.php';
+
+    /**
+     * Curl transport protocol.
+     *
+     * @var CurlTransport
+     */
+    private $transport;
+
+    /**
+     * @param CurlTransport $transport
+     */
+    public function __construct(CurlTransport $transport)
+    {
+        $this->transport = $transport;
+    }
+
+    /**
+     * Remove generated code.
+     *
+     * @return void
+     */
+    public function delete()
+    {
+        $url = $_ENV['app_frontend_url'] . self::URL;
+        $curl = $this->transport;
+        $curl->write($url, [], CurlInterface::GET);
+        $curl->read();
+        $curl->close();
+    }
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php
new file mode 100644
index 0000000000000000000000000000000000000000..f61210dc554efc910d4d44b2f5f7bed875d4955d
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Mtf\Util\Command;
+
+use Magento\Mtf\Util\Protocol\CurlInterface;
+use Magento\Mtf\Util\Protocol\CurlTransport;
+
+/**
+ * PathChecker checks that path to file or directory exists.
+ */
+class PathChecker
+{
+    /**
+     * Url to checkPath.php.
+     */
+    const URL = 'dev/tests/functional/utils/pathChecker.php';
+
+    /**
+     * Curl transport protocol.
+     *
+     * @var CurlTransport
+     */
+    private $transport;
+
+    /**
+     * @param CurlTransport $transport
+     */
+    public function __construct(CurlTransport $transport)
+    {
+        $this->transport = $transport;
+    }
+
+    /**
+     * Check that $path exists.
+     *
+     * @param string $path
+     * @return bool
+     */
+    public function pathExists($path)
+    {
+        $url = $_ENV['app_frontend_url'] . self::URL . '?path=' . urlencode($path);
+        $curl = $this->transport;
+        $curl->write($url, [], CurlInterface::GET);
+        $result = $curl->read();
+        $curl->close();
+
+        return strpos($result, 'path exists: true') !== false;
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
index 435c1b568827147859c493203bd68186ed0d8443..a569274f6121598a23afdb3aa72451b965d9953d 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd">
     <testCase name="Magento\Catalog\Test\TestCase\Product\AddToCartCrossSellTest" summary="Promote Products as Cross-Sells" ticketId="MAGETWO-12390">
         <variation name="AddToCartCrossSellTestVariation1" method="test">
-            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data>
+            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data>
             <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data>
             <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data>
             <data name="navigateProductsOrder" xsi:type="string">simple1,config1,simple2</data>
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml
index a88fb5d6a27ce8720b8df3b0523bc5c08cade8f6..f3b17309e045d37eb95b9fc7a074af4deba60063 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd">
     <testCase name="Magento\Catalog\Test\TestCase\Product\NavigateRelatedProductsTest" summary="Promote Products as Related" ticketId="MAGETWO-12392">
         <variation name="NavigateRelatedProductsTestVariation1" method="test">
-            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data>
+            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data>
             <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data>
             <data name="selectable" xsi:type="string">simple1:yes,simple2:yes,config1:no</data>
             <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data>
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml
index 045b6ae009e7b4c91124069cf54bc980a3ae2292..6e309c4932331cc45ec22db9f1e4a1750fa773a7 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd">
     <testCase name="Magento\Catalog\Test\TestCase\Product\NavigateUpSellProductsTest" summary="Promote Products as Up-Sells" ticketId="MAGETWO-12391">
         <variation name="NavigateUpSellProductsTestVariation1" method="test">
-            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data>
+            <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data>
             <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data>
             <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data>
             <data name="navigateProductsOrder" xsi:type="string">simple1,config1,simple2</data>
diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..64e46ac9b0289f6d0ee265339eff208c35fe7c3f
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Install\Test\Constraint;
+
+use Magento\Mtf\Constraint\AbstractConstraint;
+use Magento\Mtf\Util\Command\PathChecker;
+
+/**
+ * Assert that path of generated files is correct.
+ */
+class AssertGenerationFilePathCheck extends AbstractConstraint
+{
+    /**
+     * Assert that path of generated files is correct.
+     *
+     * @param PathChecker $pathChecker
+     * @return void
+     */
+    public function processAssert(PathChecker $pathChecker)
+    {
+        $existsPaths = [
+            'generated/code',
+            'generated/metadata',
+            'generated/metadata/global.ser',
+            'generated/metadata/adminhtml.ser',
+            'generated/metadata/crontab.ser',
+            'generated/metadata/frontend.ser',
+            'generated/metadata/webapi_rest.ser',
+            'generated/metadata/webapi_soap.ser',
+        ];
+
+        $nonExistsPaths = [
+            'var/di',
+            'var/generation'
+        ];
+
+        foreach ($existsPaths as $path) {
+            \PHPUnit_Framework_Assert::assertTrue(
+                $pathChecker->pathExists($path),
+                'Path "' . $path . '" does not exist.'
+            );
+        }
+
+        foreach ($nonExistsPaths as $path) {
+            \PHPUnit_Framework_Assert::assertFalse(
+                $pathChecker->pathExists($path),
+                'Path "' . $path . '" exists.'
+            );
+        }
+    }
+
+    /**
+     * Returns a string representation of the object.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'Path of generated files is correct.';
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php
index 657ea4379b46e8d106e93cdddfc978c7c4d7159a..32594facd3a1bce7a6ae59f04840c8fd8e0e0421 100644
--- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php
+++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php
@@ -17,6 +17,7 @@ use Magento\Install\Test\Constraint\AssertSuccessfulReadinessCheck;
 use Magento\Install\Test\Constraint\AssertAdminUriAutogenerated;
 use Magento\Install\Test\Constraint\AssertDevdocsLink;
 use Magento\Mtf\Util\Command\Cli\Setup;
+use Magento\Mtf\Util\Command\GeneratedCode;
 use Magento\Mtf\Client\BrowserInterface;
 
 /**
@@ -76,6 +77,13 @@ class InstallTest extends Injectable
      */
     protected $installPage;
 
+    /**
+     * Setup Magento for tests executions.
+     *
+     * @var Setup
+     */
+    private $magentoSetup;
+
     /**
      * Uninstall Magento before test.
      *
@@ -101,12 +109,17 @@ class InstallTest extends Injectable
      * @param Install $installPage
      * @param Setup $magentoSetup
      * @param DevdocsInstall $devdocsInstallPage
+     * @param GeneratedCode $generatedCode
      * @return void
      */
-    public function __inject(Install $installPage, Setup $magentoSetup, DevdocsInstall $devdocsInstallPage)
-    {
-        // Uninstall Magento.
-        $magentoSetup->uninstall();
+    public function __inject(
+        Install $installPage,
+        Setup $magentoSetup,
+        DevdocsInstall $devdocsInstallPage,
+        GeneratedCode $generatedCode
+    ) {
+        $generatedCode->delete();
+        $this->magentoSetup = $magentoSetup;
         $this->installPage = $installPage;
         $this->devdocsInstallPage = $devdocsInstallPage;
     }
@@ -122,8 +135,10 @@ class InstallTest extends Injectable
      * @param AssertAdminUriAutogenerated $assertAdminUri
      * @param AssertDevdocsLink $assertDevdocsLink
      * @param BrowserInterface $browser
+     * @param bool $diCompile
      * @param array $install [optional]
      * @return array
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
     public function test(
         User $user,
@@ -134,8 +149,10 @@ class InstallTest extends Injectable
         AssertAdminUriAutogenerated $assertAdminUri,
         AssertDevdocsLink $assertDevdocsLink,
         BrowserInterface $browser,
+        $diCompile = false,
         array $install = []
     ) {
+        $this->magentoSetup->uninstall();
         $dataConfig = array_merge($install, $configData);
         if (isset($dataConfig['httpsFront'])) {
             $dataConfig['https'] = str_replace('http', 'https', $dataConfig['baseUrl']);
@@ -178,6 +195,10 @@ class InstallTest extends Injectable
         // Step 6: Install.
         $this->installPage->getInstallBlock()->clickInstallNow();
 
+        if ($diCompile) {
+            $this->magentoSetup->diCompile();
+        }
+
         return ['installConfig' => $installConfig];
     }
 
diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml
index cdfc018346b56dad265044131f6719fb3c48ef78..cb785723763be541be0136a40cb12d3c6eecb558 100644
--- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml
@@ -48,10 +48,12 @@
             <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" />
             <constraint name="Magento\Install\Test\Constraint\AssertSecureUrlEnabled" />
         </variation>
-        <variation name="InstallTestVariation6" summary="Install with default values">
+        <variation name="InstallTestVariation6" summary="Install with default values and check DI compile" ticketId="MAGETWO-62817">
             <data name="user/dataset" xsi:type="string">default</data>
+            <data name="diCompile" xsi:type="boolean">true</data>
             <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" />
             <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" />
+            <constraint name="Magento\Install\Test\Constraint\AssertGenerationFilePathCheck" />
         </variation>
     </testCase>
 </config>
diff --git a/dev/tests/functional/utils/deleteMagentoGeneratedCode.php b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php
new file mode 100644
index 0000000000000000000000000000000000000000..b90269a9a21f68a8f2601eb409a4c7c5e912ff87
--- /dev/null
+++ b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php
@@ -0,0 +1,7 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+exec('rm -rf ../../../../generated/*');
diff --git a/dev/tests/functional/utils/pathChecker.php b/dev/tests/functional/utils/pathChecker.php
new file mode 100644
index 0000000000000000000000000000000000000000..a57ac8d3e07b1d82f777e64dddba3518cf281a94
--- /dev/null
+++ b/dev/tests/functional/utils/pathChecker.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+if (isset($_GET['path'])) {
+    $path = urldecode($_GET['path']);
+
+    if (file_exists('../../../../' . $path)) {
+        echo 'path exists: true';
+    } else {
+        echo 'path exists: false';
+    }
+} else {
+    throw new \InvalidArgumentException("GET parameter 'path' is not set.");
+}
diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php
index 3d76b9774d50976e1f74cab4979f39e1cbd86064..e52976f505710a226e4cf008b8e21341a08fecce 100644
--- a/dev/tests/integration/framework/Magento/TestFramework/Application.php
+++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php
@@ -630,12 +630,13 @@ class Application
     {
         $path = DirectoryList::PATH;
         $var = "{$this->installDir}/var";
+        $generated = "{$this->installDir}/generated";
         $customDirs = [
             DirectoryList::CONFIG => [$path => "{$this->installDir}/etc"],
             DirectoryList::VAR_DIR => [$path => $var],
             DirectoryList::MEDIA => [$path => "{$this->installDir}/pub/media"],
             DirectoryList::STATIC_VIEW => [$path => "{$this->installDir}/pub/static"],
-            DirectoryList::GENERATION => [$path => "{$var}/generation"],
+            DirectoryList::GENERATION => [$path => "{$generated}/code"],
             DirectoryList::CACHE => [$path => "{$var}/cache"],
             DirectoryList::LOG => [$path => "{$var}/log"],
             DirectoryList::SESSION => [$path => "{$var}/session"],
diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php
index 2a3441afcd2aad6d1eeab87a1c4c3e7f7c266bd2..ea450cbe81c2ff5bc4ad8a28705dc7ba97a84a82 100644
--- a/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php
+++ b/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php
@@ -34,26 +34,4 @@ class TemplateTest extends \PHPUnit_Framework_TestCase
     {
         $this->assertGreaterThan(15, strlen($this->_block->getFormKey()));
     }
-
-    /**
-     * @magentoAppArea adminhtml
-     * @covers \Magento\Backend\Block\Template::isOutputEnabled
-     * @magentoConfigFixture current_store advanced/modules_disable_output/dummy 1
-     */
-    public function testIsOutputEnabledTrue()
-    {
-        $this->_block->setData('module_name', 'dummy');
-        $this->assertFalse($this->_block->isOutputEnabled('dummy'));
-    }
-
-    /**
-     * @magentoAppArea adminhtml
-     * @covers \Magento\Backend\Block\Template::isOutputEnabled
-     * @magentoConfigFixture current_store advanced/modules_disable_output/dummy 0
-     */
-    public function testIsOutputEnabledFalse()
-    {
-        $this->_block->setData('module_name', 'dummy');
-        $this->assertTrue($this->_block->isOutputEnabled('dummy'));
-    }
 }
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..101ce61c779dcfd930a7816bcafc37c84c4d3fdc
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Observer;
+
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
+
+/**
+ * Test Cases:
+ *
+ * - has changes for url_key, is_anchor, changed product list
+ * - in global scope
+ * - in store
+ * - generate canonical
+ * - generate children
+ * - has children
+ * - generate current
+ * - has rewrites history
+ * @magentoAppArea adminhtml
+ */
+class CategoryProcessUrlRewriteSavingObserverTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var \Magento\Framework\ObjectManagerInterface */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @param array $filter
+     * @return array
+     */
+    private function getActualResults(array $filter)
+    {
+        /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */
+        $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class);
+        $actualResults = [];
+        foreach ($urlFinder->findAllByData($filter) as $url) {
+            $actualResults[] = [
+                'request_path' => $url->getRequestPath(),
+                'target_path' => $url->getTargetPath(),
+                'is_auto_generated' => (int)$url->getIsAutogenerated(),
+                'redirect_type' => $url->getRedirectType()
+            ];
+        }
+        return $actualResults;
+    }
+
+    public function tearDown()
+    {
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->delete();
+    }
+
+    /**
+     * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories.php
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testUrlKeyHasChanged()
+    {
+        $categoryFilter = [
+            UrlRewrite::ENTITY_TYPE => 'category',
+        ];
+        $expected = [
+            [
+                'request_path' => "category-1.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+        /** @var \Magento\Catalog\Model\Category $category */
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->setData('save_rewrites_history', false);
+        $category->setUrlKey('new-url');
+        $category->save();
+        $expected = [
+            [
+                'request_path' => "new-url.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "new-url/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "new-url/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+    }
+
+    /**
+     * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories_with_products.php
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testIsAnchorHasChanged()
+    {
+        $categoryFilter = [
+            UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
+        ];
+        /** @var \Magento\Catalog\Model\Category $category */
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->setData('is_anchor', false);
+        $category->save();
+        $expected = [
+            [
+                'request_path' => "category-1.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php
index cdc31a9199b6a49fbe92ff19c0298c18c64c15e5..14e4829ca9683b0a3ec875afa2aeeaf1e9d6c274 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php
@@ -56,7 +56,7 @@ class DefaultRendererTest extends \PHPUnit_Framework_TestCase
             [
                 $addressAttributes,
                 AttributeDataFactory::OUTPUT_FORMAT_PDF,
-                "John Smith|\n\nGreen str, 67\n\n\n\n\nCityM,|\nAlabama, 75477|\nUnited States|\nT: 3468676|\n|\n|"
+                "John Smith|\n\nGreen str, 67|\n\n\n\nCityM, Alabama, 75477|\nUnited States|\nT: 3468676|\n|\n|"
             ],
             [
                 $addressAttributes,
@@ -112,7 +112,7 @@ United States<br/>\nT: <a href=\"tel:3468676\">3468676</a>\n\n",
             [
                 $address,
                 AttributeDataFactory::OUTPUT_FORMAT_PDF,
-                "John Smith|\n\nGreen str, 67\n\n\n\n\nCityM,|\nAlabama, 75477|
+                "John Smith|\n\nGreen str, 67|\n\n\n\nCityM, Alabama, 75477|
 United States|\nT: 3468676|\n|\n|"
             ],
             [
diff --git a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b0fe2de866e817ab78e646087b9ebe248a60818
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Console\Command\App;
+
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory;
+use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\DeploymentConfig\Reader;
+use Magento\Framework\App\DeploymentConfig\Writer;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Filesystem;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\TestFramework\Helper\Bootstrap;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class SensitiveConfigSetCommandTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * @var Reader
+     */
+    private $reader;
+
+    /**
+     * @var ConfigFilePool
+     */
+    private $configFilePool;
+
+    /**
+     * @var array
+     */
+    private $config;
+
+    /**
+     * @var Filesystem
+     */
+    private $filesystem;
+
+    public function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+        $this->reader = $this->objectManager->get(Reader::class);
+        $this->configFilePool = $this->objectManager->get(ConfigFilePool::class);
+        $this->config = $this->loadConfig();
+        $this->filesystem = $this->objectManager->get(Filesystem::class);
+        $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile(
+            $this->getFileName(),
+            file_get_contents(__DIR__ . '/../../../_files/_config.local.php')
+        );
+    }
+
+    /**
+     * @param $scope
+     * @param $scopeCode
+     * @param callable $assertCallback
+     * @magentoDataFixture Magento/Store/_files/website.php
+     * @magentoDbIsolation enabled
+     * @dataProvider testExecuteDataProvider
+     */
+    public function testExecute($scope, $scopeCode, callable $assertCallback)
+    {
+        $outputMock = $this->getMock(OutputInterface::class);
+        $outputMock->expects($this->at(0))
+            ->method('writeln')
+            ->with('<info>Configuration value saved in app/etc/config.php</info>');
+
+        $inputMock = $this->getMock(InputInterface::class);
+        $inputMock->expects($this->exactly(2))
+            ->method('getArgument')
+            ->withConsecutive(
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH],
+                [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE]
+            )
+            ->willReturnOnConsecutiveCalls(
+                'some/config/path_two',
+                'sensitiveValue'
+            );
+        $inputMock->expects($this->exactly(3))
+            ->method('getOption')
+            ->withConsecutive(
+                [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE],
+                [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE_CODE],
+                [SensitiveConfigSetCommand::INPUT_OPTION_INTERACTIVE]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $scope,
+                $scopeCode,
+                null
+            );
+
+        /** @var SensitiveConfigSetCommand command */
+        $command = $this->objectManager->create(SensitiveConfigSetCommand::class);
+        $command->run($inputMock, $outputMock);
+
+        $config = $this->loadConfig();
+
+        $assertCallback($config);
+    }
+
+    public function testExecuteDataProvider()
+    {
+        return [
+            [
+                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
+                null,
+                function (array $config) {
+                    $this->assertTrue(isset($config['system']['default']['some']['config']['path_two']));
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['default']['some']['config']['path_two']
+                    );
+                }
+            ],
+            [
+                'website',
+                'test',
+                function (array $config) {
+                    $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_two']));
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['website']['test']['some']['config']['path_two']
+                    );
+                }
+            ]
+        ];
+    }
+
+    /**
+     * @param $scope
+     * @param $scopeCode
+     * @param callable $assertCallback
+     * @magentoDataFixture Magento/Store/_files/website.php
+     * @magentoDbIsolation enabled
+     * @dataProvider testExecuteInteractiveDataProvider
+     */
+    public function testExecuteInteractive($scope, $scopeCode, callable $assertCallback)
+    {
+        $inputMock = $this->getMock(InputInterface::class);
+        $outputMock = $this->getMock(OutputInterface::class);
+        $outputMock->expects($this->at(0))
+            ->method('writeln')
+            ->with('<info>Please set configuration values or skip them by pressing [Enter]:</info>');
+        $outputMock->expects($this->at(1))
+            ->method('writeln')
+            ->with('<info>Configuration values saved in app/etc/config.php</info>');
+        $inputMock->expects($this->exactly(3))
+            ->method('getOption')
+            ->withConsecutive(
+                [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE],
+                [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE_CODE],
+                [SensitiveConfigSetCommand::INPUT_OPTION_INTERACTIVE]
+            )
+            ->willReturnOnConsecutiveCalls(
+                $scope,
+                $scopeCode,
+                true
+            );
+
+        $questionHelperMock = $this->getMock(QuestionHelper::class);
+        $questionHelperMock->expects($this->exactly(3))
+            ->method('ask')
+            ->willReturn('sensitiveValue');
+
+        $interactiveCollectorMock = $this->objectManager->create(
+            InteractiveCollector::class,
+            [
+                'questionHelper' => $questionHelperMock
+            ]
+        );
+        $collectorFactoryMock = $this->getMockBuilder(CollectorFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $collectorFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(CollectorFactory::TYPE_INTERACTIVE)
+            ->willReturn($interactiveCollectorMock);
+
+        /** @var SensitiveConfigSetCommand command */
+        $command = $this->objectManager->create(
+            SensitiveConfigSetCommand::class,
+            [
+                'collectorFactory' => $collectorFactoryMock
+            ]
+        );
+        $command->run($inputMock, $outputMock);
+
+        $config = $this->loadConfig();
+
+        $assertCallback($config);
+    }
+
+    public function testExecuteInteractiveDataProvider()
+    {
+        return [
+            [
+                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
+                null,
+                function (array $config) {
+                    $this->assertTrue(isset($config['system']['default']['some']['config']['path_one']));
+                    $this->assertTrue(isset($config['system']['default']['some']['config']['path_two']));
+                    $this->assertTrue(isset($config['system']['default']['some']['config']['path_three']));
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['default']['some']['config']['path_one']
+                    );
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['default']['some']['config']['path_two']
+                    );
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['default']['some']['config']['path_three']
+                    );
+                }
+            ],
+            [
+                'website',
+                'test',
+                function (array $config) {
+                    $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_one']));
+                    $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_two']));
+                    $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_three']));
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['website']['test']['some']['config']['path_one']
+                    );
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['website']['test']['some']['config']['path_two']
+                    );
+                    $this->assertEquals(
+                        'sensitiveValue',
+                        $config['system']['website']['test']['some']['config']['path_three']
+                    );
+                }
+            ]
+        ];
+    }
+
+    public function tearDown()
+    {
+        $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->delete(
+            $this->getFileName()
+        );
+        $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile(
+            $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG),
+            "<?php\n return array();\n"
+        );
+        /** @var Writer $writer */
+        $writer = $this->objectManager->get(Writer::class);
+        $writer->saveConfig([ConfigFilePool::APP_CONFIG => $this->config]);
+    }
+
+    /**
+     * @return string
+     */
+    private function getFileName()
+    {
+        /** @var ConfigFilePool $configFilePool */
+        $configFilePool = $this->objectManager->get(ConfigFilePool::class);
+        $filePool = $configFilePool->getInitialFilePools();
+
+        return $filePool[ConfigFilePool::LOCAL][ConfigFilePool::APP_CONFIG];
+    }
+
+    /**
+     * @return array
+     */
+    private function loadConfig()
+    {
+        return $this->reader->loadConfigFile(
+            ConfigFilePool::APP_CONFIG,
+            $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG),
+            true
+        );
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php b/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php
new file mode 100644
index 0000000000000000000000000000000000000000..ae4630b1a2b07e021ba1e7e1e409f4e1e81a99a6
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+return [
+    'scopes' => [
+        'websites' => []
+    ],
+    /**
+     * The configuration file doesn't contain sensitive data for security reasons.
+     * Sensitive data can be stored in the following environment variables:
+     * CONFIG__DEFAULT__SOME__CONFIG__PATH_ONE for some/config/path_one
+     * CONFIG__DEFAULT__SOME__CONFIG__PATH_TWO for some/config/path_two
+     * CONFIG__DEFAULT__SOME__CONFIG__PATH_THREE for some/config/path_three
+     */
+    'system' => [
+        'default' => [
+            'web' => [],
+            'general' => []
+        ]
+    ]
+];
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php
index 23c8c100b63ef2b4f065b848693f5b6d4ac76d7c..159dc80197834af1de6635eefda9565f551760e3 100644
--- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php
@@ -96,6 +96,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
      */
     public function testGetEntityStores($productData, $categoryData, $expectedStores)
     {
+        $this->markTestSkipped('Skipped until MAGETWO-63018');
         $args = [];
         if ($productData) {
             $args['product'] = $this->objectManager->create(
@@ -121,6 +122,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
      */
     public function testGetEntityStoresProductStoresException()
     {
+        $this->markTestSkipped('Skipped until MAGETWO-63018');
         $args = [
             'product' => $this->objectManager->create(
                 \Magento\Catalog\Model\Product::class,
diff --git a/dev/tests/static/framework/autoload.php b/dev/tests/static/framework/autoload.php
index a5750d9303c945e6f831c23b88d919b96d50cd44..fc3b62fc97287d9be6d6f72db106436c4ab7f84f 100644
--- a/dev/tests/static/framework/autoload.php
+++ b/dev/tests/static/framework/autoload.php
@@ -4,6 +4,8 @@
  * See COPYING.txt for license details.
  */
 
+use \Magento\Framework\App\Filesystem\DirectoryList;
+
 $baseDir = realpath(__DIR__ . '/../../../../');
 require $baseDir . '/app/autoload.php';
 $testsBaseDir = $baseDir . '/dev/tests/static';
@@ -16,4 +18,6 @@ $autoloadWrapper->addPsr4(
         $testsBaseDir . '/../integration/framework/Magento/TestFramework/',
     ]
 );
-$autoloadWrapper->addPsr4('Magento\\', $baseDir . '/var/generation/Magento/');
+
+$generatedCode = DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH];
+$autoloadWrapper->addPsr4('Magento\\', $baseDir . '/' . $generatedCode . '/Magento/');
diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
index 3afe3af79b14ff3177a0dc1c5ea9fe42e93b6331..ebe8501337efff410bba83f4f291b3433247a893 100644
--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
@@ -109,7 +109,7 @@ Magento/SalesRule/Model/Resource/Report/Rule
 Magento/SalesRule/Model/Resource/Rule
 Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit
 Magento/User/Block/User/Edit
-var/generation
+generated/code
 Magento/Newsletter/Block/Adminhtml/Template/Grid/Renderer
 Magento/Newsletter/Model/Template/Filter
 Magento/Newsletter/Model/Resource/Subscriber
diff --git a/dev/tests/unit/framework/autoload.php b/dev/tests/unit/framework/autoload.php
index 4b2adbe17673fb7f33a60f7183ffd190833d2904..b4987145872d44c854c993fad60cb6ce02422bf0 100644
--- a/dev/tests/unit/framework/autoload.php
+++ b/dev/tests/unit/framework/autoload.php
@@ -4,10 +4,12 @@
  * See COPYING.txt for license details.
  */
 
+use Magento\Framework\App\Filesystem\DirectoryList;
+
 $autoloader = new \Magento\Framework\TestFramework\Unit\Autoloader\ExtensionGeneratorAutoloader(
     new \Magento\Framework\Code\Generator\Io(
         new \Magento\Framework\Filesystem\Driver\File(),
-        TESTS_TEMP_DIR . '/var/generation'
+        TESTS_TEMP_DIR . '/'. DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH]
     )
 );
 spl_autoload_register([$autoloader, 'load']);
diff --git a/generated/.htaccess b/generated/.htaccess
new file mode 100644
index 0000000000000000000000000000000000000000..896fbc5a341ea313ff3abd503808842757678c1d
--- /dev/null
+++ b/generated/.htaccess
@@ -0,0 +1,2 @@
+Order deny,allow
+Deny from all
\ No newline at end of file
diff --git a/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php b/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..efc917d31b0c70a1c0ee6a4b8a070f71fdbb322e
--- /dev/null
+++ b/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\App\Config;
+
+use Magento\Framework\Exception\FileSystemException;
+
+/**
+ * Interface CommentParserInterface
+ */
+interface CommentParserInterface
+{
+    /**
+     * Retrieve config list from file comments.
+     *
+     * @param string $fileName
+     * @return array
+     * @throws FileSystemException
+     */
+    public function execute($fileName);
+}
diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php
index ae8a9bd4dc64e66ecf8ab865f61541d9bdc94a6b..28fcbd3692c9e1dffb84b0a2ef98b1ffb0633d96 100644
--- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php
+++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php
@@ -25,7 +25,10 @@ class PhpFormatter implements FormatterInterface
             foreach ($data as $key => $value) {
                 $comment = '  ';
                 if (!empty($comments[$key])) {
-                    $comment = "  /**\n * " . str_replace("\n", "\n * ", var_export($comments[$key], true)) . "\n */\n";
+                    $exportedComment = is_string($comments[$key])
+                        ? $comments[$key]
+                        : var_export($comments[$key], true);
+                    $comment = "  /**\n * " . str_replace("\n", "\n * ", $exportedComment) . "\n */\n";
                 }
                 $space = is_array($value) ? " \n" : ' ';
                 $elements[] = $comment . var_export($key, true) . ' =>' . $space . var_export($value, true);
diff --git a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php
index ae1a6544ef2cbf05ef8bd4de84188472113ffa9c..57c903b49198cb5b4c99c8b6dcb2fcd8bcb3ed94 100644
--- a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php
+++ b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php
@@ -5,6 +5,8 @@
  */
 namespace Magento\Framework\App\Filesystem;
 
+use Magento\Framework\Code\Generator\Io;
+
 /**
  * A Magento application specific list of directories
  */
@@ -120,8 +122,8 @@ class DirectoryList extends \Magento\Framework\Filesystem\DirectoryList
             self::VAR_DIR => [parent::PATH => 'var'],
             self::CACHE => [parent::PATH => 'var/cache'],
             self::LOG => [parent::PATH => 'var/log'],
-            self::DI => [parent::PATH => 'var/di'],
-            self::GENERATION => [parent::PATH => 'var/generation'],
+            self::DI => [parent::PATH => 'generated/metadata'],
+            self::GENERATION => [parent::PATH => Io::DEFAULT_DIRECTORY],
             self::SESSION => [parent::PATH => 'var/session'],
             self::MEDIA => [parent::PATH => 'pub/media', parent::URL_PATH => 'pub/media'],
             self::STATIC_VIEW => [parent::PATH => 'pub/static', parent::URL_PATH => 'pub/static'],
diff --git a/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php b/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..8063d61d8f5b29395183b9742dc45d6084957955
--- /dev/null
+++ b/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\App\Scope;
+
+use Magento\Framework\Exception\LocalizedException;
+
+interface ValidatorInterface
+{
+    /**
+     * Validate if exists given scope and scope code
+     * otherwise, throws an exception with appropriate message.
+     *
+     * @param string $scope
+     * @param string $scopeCode
+     * @return boolean
+     * @throws LocalizedException
+     */
+    public function isValid($scope, $scopeCode = null);
+}
diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php
index 0f86b778a04d2d1c52535adf4f6eafb7d5af2795..9f6894c0a68350e86b9e96a8111de123ac11d574 100644
--- a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php
+++ b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php
@@ -3,7 +3,6 @@
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
 namespace Magento\Framework\App\Test\Unit\DeploymentConfig\Writer;
 
 use \Magento\Framework\App\DeploymentConfig\Writer\PhpFormatter;
@@ -48,9 +47,9 @@ class PhpFormatterTest extends \PHPUnit_Framework_TestCase
         ];
         $comments1 = ['ns2' => 'comment for namespace 2'];
         $comments2 = [
-            'ns1' => 'comment for namespace 1',
-            'ns2' => "comment for namespace 2.\nNext comment for namespace 2",
-            'ns3' => 'comment for namespace 3',
+            'ns1' => 'comment for\' namespace 1',
+            'ns2' => "comment for namespace 2.\nNext comment for' namespace 2",
+            'ns3' => 'comment for" namespace 3',
             'ns4' => 'comment for namespace 4',
             'ns5' => 'comment for unexisted namespace 5',
         ];
@@ -71,7 +70,7 @@ return array (
     ),
   ),
   /**
-   * 'comment for namespace 2'
+   * comment for namespace 2
    */
   'ns2' => 
   array (
@@ -89,7 +88,7 @@ TEXT;
 <?php
 return array (
   /**
-   * 'comment for namespace 1'
+   * comment for' namespace 1
    */
   'ns1' => 
   array (
@@ -105,8 +104,8 @@ return array (
     ),
   ),
   /**
-   * 'comment for namespace 2.
-   * Next comment for namespace 2'
+   * comment for namespace 2.
+   * Next comment for' namespace 2
    */
   'ns2' => 
   array (
@@ -116,11 +115,11 @@ return array (
     ),
   ),
   /**
-   * 'comment for namespace 3'
+   * comment for" namespace 3
    */
   'ns3' => 'just text',
   /**
-   * 'comment for namespace 4'
+   * comment for namespace 4
    */
   'ns4' => 'just text'
 );
diff --git a/lib/internal/Magento/Framework/Code/GeneratedFiles.php b/lib/internal/Magento/Framework/Code/GeneratedFiles.php
index e33b311e74c629735d60485874c42769ad12a11e..9656ef6c4b9fe794b4ca6f915027905f106c1e3d 100644
--- a/lib/internal/Magento/Framework/Code/GeneratedFiles.php
+++ b/lib/internal/Magento/Framework/Code/GeneratedFiles.php
@@ -57,7 +57,7 @@ class GeneratedFiles
     }
 
     /**
-     * Clean var/generation, var/di and var/cache
+     * Clean generated/code, generated/metadata and var/cache
      *
      * @return void
      */
@@ -81,12 +81,12 @@ class GeneratedFiles
             $generationPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::GENERATION));
             $diPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::DI));
 
-            // Clean var/generation dir
+            // Clean generated/code dir
             if ($this->write->isDirectory($generationPath)) {
                 $this->write->delete($generationPath);
             }
 
-            // Clean var/di
+            // Clean generated/metadata
             if ($this->write->isDirectory($diPath)) {
                 $this->write->delete($diPath);
             }
@@ -101,7 +101,7 @@ class GeneratedFiles
     }
 
     /**
-     * Create flag for cleaning up var/generation, var/di and var/cache directories for subsequent
+     * Create flag for cleaning up generated/code, generated/metadata and var/cache directories for subsequent
      * regeneration of this content
      *
      * @return void
diff --git a/lib/internal/Magento/Framework/Code/Generator/Io.php b/lib/internal/Magento/Framework/Code/Generator/Io.php
index 05ddb46e948325eaba29f91a570ddb8412948e40..85449fe61749fa6dcdbb941a5b28c436cf663e90 100644
--- a/lib/internal/Magento/Framework/Code/Generator/Io.php
+++ b/lib/internal/Magento/Framework/Code/Generator/Io.php
@@ -14,7 +14,7 @@ class Io
      * Default code generation directory
      * Should correspond the value from \Magento\Framework\Filesystem
      */
-    const DEFAULT_DIRECTORY = 'var/generation';
+    const DEFAULT_DIRECTORY = 'generated/code';
 
     /**
      * Path to directory where new file must be created
diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php
index 5a89d0f6b60571680068463df5b78f85951c809c..855e792ec21b9923ce9020fde7ac3eb41040d0bb 100644
--- a/lib/internal/Magento/Framework/Console/Cli.php
+++ b/lib/internal/Magento/Framework/Console/Cli.php
@@ -5,102 +5,106 @@
  */
 namespace Magento\Framework\Console;
 
+use Magento\Framework\App\DeploymentConfig;
 use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\App\State;
 use Magento\Framework\Composer\ComposerJsonFinder;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Output\ConsoleOutput;
-use Symfony\Component\Console\Input\ArgvInput;
-use Symfony\Component\Console\Application as SymfonyApplication;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Setup\Model\ObjectManagerProvider;
+use Symfony\Component\Console;
 use Magento\Framework\App\Bootstrap;
 use Magento\Framework\Filesystem\Driver\File;
 use Magento\Framework\Shell\ComplexParameter;
 use Magento\Setup\Console\CompilerPreparation;
-use \Magento\Framework\App\ProductMetadata;
+use Magento\Framework\App\ProductMetadata;
+use Magento\Framework\ObjectManagerInterface;
+use Zend\ServiceManager\ServiceManager;
 
 /**
- * Magento 2 CLI Application. This is the hood for all command line tools supported by Magento
+ * Magento 2 CLI Application.
+ * This is the hood for all command line tools supported by Magento.
  *
  * {@inheritdoc}
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
-class Cli extends SymfonyApplication
+class Cli extends Console\Application
 {
     /**
-     * Name of input option
+     * Name of input option.
      */
     const INPUT_KEY_BOOTSTRAP = 'bootstrap';
 
-    /**
-     * Cli exit codes
+    /**#@+
+     * Cli exit codes.
      */
     const RETURN_SUCCESS = 0;
     const RETURN_FAILURE = 1;
+    /**#@-*/
 
-    /** @var \Zend\ServiceManager\ServiceManager */
+    /**
+     * Service Manager.
+     *
+     * @var ServiceManager
+     */
     private $serviceManager;
 
     /**
-     * Initialization exception
+     * Initialization exception.
      *
      * @var \Exception
      */
     private $initException;
 
     /**
-     * @param string $name  application name
-     * @param string $version application version
-     * @SuppressWarnings(PHPMD.ExitExpression)
+     * Object Manager.
+     *
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * @param string $name the application name
+     * @param string $version the application version
      */
     public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
     {
         $this->serviceManager = \Zend\Mvc\Application::init(require BP . '/setup/config/application.config.php')
             ->getServiceManager();
-        $generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager);
-        if (!$generationDirectoryAccess->check()) {
-            $output = new ConsoleOutput();
-            $output->writeln(
-                '<error>Command line user does not have read and write permissions on var/generation directory.  Please'
-                . ' address this issue before using Magento command line.</error>'
-            );
-            exit(0);
-        }
-        /**
-         * Temporary workaround until the compiler is able to clear the generation directory
-         * @todo remove after MAGETWO-44493 resolved
-         */
-        if (class_exists(CompilerPreparation::class)) {
-            $compilerPreparation = new CompilerPreparation($this->serviceManager, new ArgvInput(), new File());
-            $compilerPreparation->handleCompilerEnvironment();
-        }
+
+        $this->assertCompilerPreparation();
+        $this->initObjectManager();
+        $this->assertGenerationPermissions();
 
         if ($version == 'UNKNOWN') {
-            $directoryList      = new DirectoryList(BP);
+            $directoryList = new DirectoryList(BP);
             $composerJsonFinder = new ComposerJsonFinder($directoryList);
-            $productMetadata    = new ProductMetadata($composerJsonFinder);
+            $productMetadata = new ProductMetadata($composerJsonFinder);
             $version = $productMetadata->getVersion();
         }
+
         parent::__construct($name, $version);
     }
 
     /**
-     * Process an error happened during initialization of commands, if any
+     * {@inheritdoc}
      *
-     * @param InputInterface $input
-     * @param OutputInterface $output
-     * @return int
-     * @throws \Exception
+     * @throws \Exception the exception in case of unexpected error
      */
-    public function doRun(InputInterface $input, OutputInterface $output)
+    public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
     {
         $exitCode = parent::doRun($input, $output);
+
         if ($this->initException) {
             $output->writeln(
                 "<error>We're sorry, an error occurred. Try clearing the cache and code generation directories. "
-                . "By default, they are: var/cache, var/di, var/generation, and var/page_cache.</error>"
+                . "By default, they are: " . $this->getDefaultDirectoryPath(DirectoryList::CACHE) . ", "
+                . $this->getDefaultDirectoryPath(DirectoryList::DI) . ", "
+                . $this->getDefaultDirectoryPath(DirectoryList::GENERATION) . ", and var/page_cache.</error>"
             );
+
             throw $this->initException;
         }
+
         return $exitCode;
     }
 
@@ -113,46 +117,142 @@ class Cli extends SymfonyApplication
     }
 
     /**
-     * Gets application commands
+     * Gets application commands.
      *
-     * @return array
+     * @return array a list of available application commands
      */
     protected function getApplicationCommands()
     {
         $commands = [];
         try {
-            $bootstrapParam = new ComplexParameter(self::INPUT_KEY_BOOTSTRAP);
-            $params = $bootstrapParam->mergeFromArgv($_SERVER, $_SERVER);
-            $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null;
-            $bootstrap = Bootstrap::create(BP, $params);
-            $objectManager = $bootstrap->getObjectManager();
-            /** @var \Magento\Setup\Model\ObjectManagerProvider $omProvider */
-            $omProvider = $this->serviceManager->get(\Magento\Setup\Model\ObjectManagerProvider::class);
-            $omProvider->setObjectManager($objectManager);
-
             if (class_exists(\Magento\Setup\Console\CommandList::class)) {
                 $setupCommandList = new \Magento\Setup\Console\CommandList($this->serviceManager);
                 $commands = array_merge($commands, $setupCommandList->getCommands());
             }
 
-            if ($objectManager->get(\Magento\Framework\App\DeploymentConfig::class)->isAvailable()) {
-                /** @var \Magento\Framework\Console\CommandListInterface */
-                $commandList = $objectManager->create(\Magento\Framework\Console\CommandListInterface::class);
+            if ($this->objectManager->get(DeploymentConfig::class)->isAvailable()) {
+                /** @var CommandListInterface */
+                $commandList = $this->objectManager->create(CommandListInterface::class);
                 $commands = array_merge($commands, $commandList->getCommands());
             }
 
-            $commands = array_merge($commands, $this->getVendorCommands($objectManager));
+            $commands = array_merge(
+                $commands,
+                $this->getVendorCommands($this->objectManager)
+            );
         } catch (\Exception $e) {
             $this->initException = $e;
         }
+
         return $commands;
     }
 
     /**
-     * Gets vendor commands
+     * Object Manager initialization.
      *
-     * @param \Magento\Framework\ObjectManagerInterface $objectManager
-     * @return array
+     * @return void
+     * @SuppressWarnings(PHPMD.ExitExpression)
+     */
+    private function initObjectManager()
+    {
+        try {
+            $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER);
+            $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null;
+
+            $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager();
+
+            /** @var ObjectManagerProvider $omProvider */
+            $omProvider = $this->serviceManager->get(ObjectManagerProvider::class);
+            $omProvider->setObjectManager($this->objectManager);
+        } catch (FileSystemException $exception) {
+            $this->writeGenerationDirectoryReadError();
+
+            exit(static::RETURN_FAILURE);
+        }
+    }
+
+    /**
+     * Checks whether generation directory is read-only.
+     * Depends on the current mode:
+     *      production - application will proceed
+     *      default - application will be terminated
+     *      developer - application will be terminated
+     *
+     * @return void
+     * @SuppressWarnings(PHPMD.ExitExpression)
+     */
+    private function assertGenerationPermissions()
+    {
+        /** @var GenerationDirectoryAccess $generationDirectoryAccess */
+        $generationDirectoryAccess = $this->objectManager->create(
+            GenerationDirectoryAccess::class,
+            ['serviceManager' => $this->serviceManager]
+        );
+        /** @var State $state */
+        $state = $this->objectManager->get(State::class);
+
+        if (
+            $state->getMode() !== State::MODE_PRODUCTION
+            && !$generationDirectoryAccess->check()
+        ) {
+            $this->writeGenerationDirectoryReadError();
+
+            exit(static::RETURN_FAILURE);
+        }
+    }
+
+    /**
+     * Checks whether compiler is being prepared.
+     *
+     * @return void
+     * @SuppressWarnings(PHPMD.ExitExpression)
+     */
+    private function assertCompilerPreparation()
+    {
+        /**
+         * Temporary workaround until the compiler is able to clear the generation directory
+         * @todo remove after MAGETWO-44493 resolved
+         */
+        if (class_exists(CompilerPreparation::class)) {
+            $compilerPreparation = new CompilerPreparation(
+                $this->serviceManager,
+                new Console\Input\ArgvInput(),
+                new File()
+            );
+
+            try {
+                $compilerPreparation->handleCompilerEnvironment();
+            } catch (FileSystemException $e) {
+                $this->writeGenerationDirectoryReadError();
+
+                exit(static::RETURN_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Writes read error to console.
+     *
+     * @return void
+     */
+    private function writeGenerationDirectoryReadError()
+    {
+        $output = new \Symfony\Component\Console\Output\ConsoleOutput();
+        $output->writeln(
+            '<error>'
+            . 'Command line user does not have read and write permissions on '
+            . $this->getDefaultDirectoryPath(DirectoryList::GENERATION) . ' directory. '
+            . 'Please address this issue before using Magento command line.'
+            . '</error>'
+        );
+    }
+
+    /**
+     * Retrieves vendor commands.
+     *
+     * @param ObjectManagerInterface $objectManager the object manager
+     *
+     * @return array an array with external commands
      */
     protected function getVendorCommands($objectManager)
     {
@@ -165,6 +265,25 @@ class Cli extends SymfonyApplication
                 );
             }
         }
+
         return $commands;
     }
+
+    /**
+     * Get default directory path by code
+     *
+     * @param string $code
+     * @return string
+     */
+    private function getDefaultDirectoryPath($code)
+    {
+        $config = DirectoryList::getDefaultConfig();
+        $result = '';
+
+        if (isset($config[$code][DirectoryList::PATH])) {
+            $result = $config[$code][DirectoryList::PATH];
+        }
+
+        return $result;
+    }
 }
diff --git a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
index b0baf410d669f16eab5899e07b9e6103a274bd82..a2a1fc9be20e0a4702734cb7065011aab63ab564 100644
--- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
+++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php
@@ -14,7 +14,7 @@ use Zend\ServiceManager\ServiceManager;
 use Magento\Setup\Mvc\Bootstrap\InitParamListener;
 
 /**
- * Check var/generation read and write access
+ * Check generated/code read and write access
  */
 class GenerationDirectoryAccess
 {
@@ -33,7 +33,7 @@ class GenerationDirectoryAccess
     }
 
     /**
-     * Check var/generation read and write access
+     * Check generated/code read and write access
      *
      * @return bool
      */
diff --git a/lib/internal/Magento/Framework/DB/TemporaryTableService.php b/lib/internal/Magento/Framework/DB/TemporaryTableService.php
new file mode 100644
index 0000000000000000000000000000000000000000..36a96f69ad67c6d46527121c377a2b2500ea309c
--- /dev/null
+++ b/lib/internal/Magento/Framework/DB/TemporaryTableService.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\DB;
+
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Select;
+
+/**
+ * Class TemporaryTableService creates a temporary table in mysql from a Magento\Framework\DB\Select.
+ * Use this class to create an index with that that you want to query later for quick data access
+ *
+ * @api
+ */
+class TemporaryTableService
+{
+    const INDEX_METHOD_HASH = 'HASH';
+    const DB_ENGINE_INNODB = 'INNODB';
+
+    /**
+     * @var string[]
+     */
+    private $allowedIndexMethods;
+
+    /**
+     * @var string[]
+     */
+    private $allowedEngines;
+
+    /**
+     * @var \Magento\Framework\Math\Random
+     */
+    private $random;
+
+    /**
+     * @var AdapterInterface[]
+     */
+    private $createdTableAdapters = [];
+
+    /**
+     * @param \Magento\Framework\Math\Random $random
+     * @param string[] $allowedIndexMethods
+     * @param string[] $allowedEngines
+     */
+    public function __construct(
+        \Magento\Framework\Math\Random $random,
+        $allowedIndexMethods = [],
+        $allowedEngines = []
+    ) {
+        $this->random = $random;
+        $this->allowedIndexMethods = $allowedIndexMethods;
+        $this->allowedEngines = $allowedEngines;
+    }
+
+    /**
+     * Creates a temporary table from select removing duplicate rows if you have a union in your select
+     * This method should always be paired with dropTable to ensure cleanup
+     * Make sure you index your data so you can query it fast
+     * You can choose from memory or file table and provide indexes to ensure fast data query
+     *
+     * Example: createFromSelect(
+     *           $selectObject,
+     *           $this->resourceConnection->getConnection(),
+     *           [
+     *              'PRIMARY' => ['primary_id'],
+     *              'some_single_field_index' => ['field'],
+     *              'UNQ_some_multiple_field_index' => ['field1', 'field2'],
+     *           ]
+     *          )
+     * Note that indexes names with UNQ_ prefix, will be created as unique
+     *
+     * @param Select $select
+     * @param AdapterInterface $adapter
+     * @param array $indexes
+     * @param string $indexMethod
+     * @param string $dbEngine
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public function createFromSelect(
+        Select $select,
+        AdapterInterface $adapter,
+        array $indexes = [],
+        $indexMethod = self::INDEX_METHOD_HASH,
+        $dbEngine = self::DB_ENGINE_INNODB
+    ) {
+        if (!in_array($indexMethod, $this->allowedIndexMethods)) {
+            throw new \InvalidArgumentException(
+                sprintf('indexMethod must be one of %s', implode(',', $this->allowedIndexMethods))
+            );
+        }
+
+        if (!in_array($dbEngine, $this->allowedEngines)) {
+            throw new \InvalidArgumentException(
+                sprintf('dbEngine must be one of %s', implode(',', $this->allowedEngines))
+            );
+        }
+
+        $name = $this->random->getUniqueHash('tmp_select_');
+
+        $indexStatements = [];
+        foreach ($indexes as $indexName => $columns) {
+            $renderedColumns = implode(',', array_map([$adapter, 'quoteIdentifier'], $columns));
+
+            $indexType = sprintf(
+                'INDEX %s USING %s',
+                $adapter->quoteIdentifier($indexName),
+                $indexMethod
+            );
+
+            if ($indexName === 'PRIMARY') {
+                $indexType = 'PRIMARY KEY';
+            } elseif (strpos($indexName, 'UNQ_') === 0) {
+                $indexType = sprintf('UNIQUE %s', $adapter->quoteIdentifier($indexName));
+            }
+
+            $indexStatements[] = sprintf('%s(%s)', $indexType, $renderedColumns);
+        }
+
+        $statement = sprintf(
+            'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)',
+            $adapter->quoteIdentifier($name),
+            $indexStatements ? '(' . implode(',', $indexStatements) . ')' : '',
+            $adapter->quoteIdentifier($dbEngine),
+            "{$select}"
+        );
+
+        $adapter->query(
+            $statement,
+            $select->getBind()
+        );
+
+        $this->createdTableAdapters[$name] = $adapter;
+
+        return $name;
+    }
+
+    /**
+     * Method used to drop a table by name
+     * This class will hold all temporary table names in createdTableAdapters array
+     * so we can dispose them once we're finished
+     *
+     * Example: dropTable($name)
+     * where $name is a variable that holds the name for a previously created temporary  table
+     * by using "createFromSelect" method
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function dropTable($name)
+    {
+        if (!empty($this->createdTableAdapters)) {
+            if (isset($this->createdTableAdapters[$name]) && !empty($name)) {
+                $adapter = $this->createdTableAdapters[$name];
+                $adapter->dropTemporaryTable($name);
+                unset($this->createdTableAdapters[$name]);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..620a197386fde6bcfa923fff03a3919511ce3cb5
--- /dev/null
+++ b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\DB\Test\Unit;
+
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Select;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\Math\Random;
+
+class TemporaryTableServiceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $temporaryTableService;
+
+    /**
+     * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $adapterMock;
+
+    /**
+     * @var Random|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $randomMock;
+
+    /**
+     * @var Select|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $selectMock;
+
+    /**
+     * Set up
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->adapterMock = $this->getMock(AdapterInterface::class);
+        $this->selectMock = $this->getMock(Select::class, [], [], '', false);
+        $this->randomMock = $this->getMock(Random::class, [], [], '', false);
+        $this->temporaryTableService = (new ObjectManager($this))->getObject(
+            TemporaryTableService::class,
+            [
+                'random' => $this->randomMock,
+                'allowedIndexMethods' => ['HASH'],
+                'allowedEngines' => ['INNODB']
+            ]
+        );
+    }
+
+    /**
+     * Run test createFromSelect method
+     *
+     * @return void
+     */
+    public function testCreateFromSelectWithException()
+    {
+        $this->setExpectedException(\InvalidArgumentException::class);
+        $random = 'random_table';
+        $indexes = [
+            ['PRIMARY' => ['primary_column_name']],
+            'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE '
+            . '(select * from sometable)'
+        ];
+
+        $this->assertEquals(
+            $random,
+            $this->temporaryTableService->createFromSelect(
+                $this->selectMock,
+                $this->adapterMock,
+                $indexes,
+                TemporaryTableService::INDEX_METHOD_HASH . "Other",
+                TemporaryTableService::DB_ENGINE_INNODB . "Other"
+            )
+        );
+    }
+
+    /**
+     * Run test createFromSelect method
+     *
+     * @param array $indexes
+     * @param string $expectedSelect
+     * @dataProvider createFromSelectDataProvider
+     * @return void
+     */
+    public function testCreateFromSelect($indexes, $expectedSelect)
+    {
+        $selectString = 'select * from sometable';
+        $random = 'random_table';
+
+        $this->randomMock->expects($this->once())
+            ->method('getUniqueHash')
+            ->willReturn($random);
+
+        $this->adapterMock->expects($this->once())
+            ->method('query')
+            ->with($expectedSelect)
+            ->willReturnSelf();
+
+        $this->adapterMock->expects($this->once())
+            ->method('query')
+            ->willReturnSelf();
+
+        $this->adapterMock->expects($this->any())
+            ->method('quoteIdentifier')
+            ->willReturnArgument(0);
+
+        $this->selectMock->expects($this->once())
+            ->method('getBind')
+            ->willReturn(['bind']);
+
+        $this->selectMock->expects($this->any())
+            ->method('__toString')
+            ->willReturn($selectString);
+
+        $this->assertEquals(
+            $random,
+            $this->temporaryTableService->createFromSelect(
+                $this->selectMock,
+                $this->adapterMock,
+                $indexes
+            )
+        );
+    }
+
+    /**
+     * Run test dropTable method when createdTables array of TemporaryTableService is empty
+     *
+     * @return void
+     */
+    public function testDropTableWhenCreatedTablesArrayIsEmpty()
+    {
+        $this->assertFalse($this->temporaryTableService->dropTable('tmp_select_table'));
+    }
+
+    /**
+     * Run test dropTable method when data exists in createdTables array of TemporaryTableService
+     *
+     * @param string $tableName
+     * @param bool $assertion
+     *
+     * @dataProvider dropTableWhenCreatedTablesArrayNotEmptyDataProvider
+     * @return void
+     */
+    public function testDropTableWhenCreatedTablesArrayNotEmpty($tableName, $assertion)
+    {
+        $createdTableAdapters = new \ReflectionProperty($this->temporaryTableService, 'createdTableAdapters');
+        $createdTableAdapters->setAccessible(true);
+        $createdTableAdapters->setValue($this->temporaryTableService, ['tmp_select_table' => $this->adapterMock]);
+        $createdTableAdapters->setAccessible(false);
+
+        $this->adapterMock->expects($this->any())
+            ->method('dropTemporaryTable')
+            ->willReturn(true);
+
+        $this->assertEquals($this->temporaryTableService->dropTable($tableName), $assertion);
+    }
+
+    /**
+     * @return array
+     */
+    public function createFromSelectDataProvider()
+    {
+        return [
+            [
+                ['PRIMARY' => ['primary_column_name']],
+                'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                ['UNQ_INDX' => ['column1', 'column2']],
+                'CREATE TEMPORARY TABLE random_table (UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                ['OTH_INDX' => ['column3', 'column4']],
+                'CREATE TEMPORARY TABLE random_table (INDEX OTH_INDX USING HASH(column3,column4)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                [
+                    'PRIMARY' => ['primary_column_name'],
+                    'OTH_INDX' => ['column3', 'column4'],
+                    'UNQ_INDX' => ['column1', 'column2']
+                ],
+                'CREATE TEMPORARY TABLE random_table '
+                . '(PRIMARY KEY(primary_column_name),'
+                . 'INDEX OTH_INDX USING HASH(column3,column4),UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+        ];
+    }
+
+    /**
+     * @return array
+     */
+    public function dropTableWhenCreatedTablesArrayNotEmptyDataProvider()
+    {
+        return [
+            ['tmp_select_table_1', false],
+            ['tmp_select_table', true],
+        ];
+    }
+}
diff --git a/lib/internal/Magento/Framework/Module/Manager.php b/lib/internal/Magento/Framework/Module/Manager.php
index 7df83917075651eb63f167aa759f9c931ba5edd7..cf22583184b1c89c79da06513a59c588a09faf25 100644
--- a/lib/internal/Magento/Framework/Module/Manager.php
+++ b/lib/internal/Magento/Framework/Module/Manager.php
@@ -3,90 +3,94 @@
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
+namespace Magento\Framework\Module;
 
 /**
- * Module statuses manager
+ * Module status manager.
+ *
+ * Usage:
+ *
+ *  $manager->isEnabled('Vendor_Module')
  */
-namespace Magento\Framework\Module;
-
 class Manager
 {
     /**
-     * @var Output\ConfigInterface
+     * The checker of output modules.
+     *
+     * @var Output\ConfigInterface the config checker of output modules.
+     * @deprecated
      */
-    private $_outputConfig;
+    private $outputConfig;
 
     /**
-     * @var ModuleListInterface
+     * The list of all modules.
+     *
+     * @var ModuleListInterface the list of all modules.
      */
-    private $_moduleList;
+    private $moduleList;
 
     /**
-     * @var array
+     * The list of config paths to ignore.
+     *
+     * @var array the list of config paths to ignore.
+     * @deprecated
      */
-    private $_outputConfigPaths;
+    private $outputConfigPaths;
 
     /**
-     * @param Output\ConfigInterface $outputConfig
-     * @param ModuleListInterface $moduleList
-     * @param array $outputConfigPaths
+     * Constructor.
+     *
+     * @param Output\ConfigInterface $outputConfig the checker of output modules
+     * @param ModuleListInterface $moduleList the list of all modules
+     * @param array $outputConfigPaths the list of config paths to ignore
      */
     public function __construct(
         Output\ConfigInterface $outputConfig,
         ModuleListInterface $moduleList,
         array $outputConfigPaths = []
     ) {
-        $this->_outputConfig = $outputConfig;
-        $this->_moduleList = $moduleList;
-        $this->_outputConfigPaths = $outputConfigPaths;
+        $this->outputConfig = $outputConfig;
+        $this->moduleList = $moduleList;
+        $this->outputConfigPaths = $outputConfigPaths;
     }
 
     /**
-     * Whether a module is enabled in the configuration or not
+     * Checks whether a module is enabled in the configuration or not.
      *
-     * @param string $moduleName Fully-qualified module name
-     * @return boolean
+     * @param string $moduleName the fully-qualified module name
+     *
+     * @return boolean true if module is enabled, false otherwise
      */
     public function isEnabled($moduleName)
     {
-        return $this->_moduleList->has($moduleName);
+        return $this->moduleList->has($moduleName);
     }
 
     /**
-     * Whether a module output is permitted by the configuration or not
+     * Checks whether a module output is permitted by the configuration or not.
+     *
+     * @param string $moduleName the fully-qualified module name.
      *
-     * @param string $moduleName Fully-qualified module name
      * @return boolean
+     * @deprecated
+     * @see \Magento\Framework\Module\Manager::isEnabled()
      */
     public function isOutputEnabled($moduleName)
     {
-        if (!$this->isEnabled($moduleName)) {
-            return false;
-        }
-        if (!$this->_isCustomOutputConfigEnabled($moduleName)) {
-            return false;
-        }
-        if ($this->_outputConfig->isEnabled($moduleName)) {
-            return false;
-        }
-        return true;
+        return $this->isEnabled($moduleName);
     }
 
     /**
-     * Whether a configuration switch for a module output permits output or not
+     * Checks whether a configuration switch for a module output permits output.
      *
      * @param string $moduleName Fully-qualified module name
+     *
      * @return boolean
+     * @deprecated
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     protected function _isCustomOutputConfigEnabled($moduleName)
     {
-        if (isset($this->_outputConfigPaths[$moduleName])) {
-            $configPath = $this->_outputConfigPaths[$moduleName];
-            if (defined($configPath)) {
-                $configPath = constant($configPath);
-            }
-            return $this->_outputConfig->isSetFlag($configPath);
-        }
         return true;
     }
 }
diff --git a/lib/internal/Magento/Framework/Module/Output/Config.php b/lib/internal/Magento/Framework/Module/Output/Config.php
index 27a99fdff4121c6514b51533d9e6626fb5f579f8..ca9d8d742d05cd410409f17161649471b17e08b1 100644
--- a/lib/internal/Magento/Framework/Module/Output/Config.php
+++ b/lib/internal/Magento/Framework/Module/Output/Config.php
@@ -7,6 +7,9 @@
  */
 namespace Magento\Framework\Module\Output;
 
+/**
+ * @deprecated
+ */
 class Config implements \Magento\Framework\Module\Output\ConfigInterface
 {
     /**
@@ -41,7 +44,7 @@ class Config implements \Magento\Framework\Module\Output\ConfigInterface
      */
     public function isEnabled($moduleName)
     {
-        return $this->isSetFlag(sprintf(self::XML_PATH_MODULE_OUTPUT_STATUS, $moduleName));
+        return false;
     }
 
     /**
@@ -49,6 +52,6 @@ class Config implements \Magento\Framework\Module\Output\ConfigInterface
      */
     public function isSetFlag($path)
     {
-        return $this->_scopeConfig->isSetFlag($path, $this->_storeType);
+        return false;
     }
 }
diff --git a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php
index e417391ed547455f1d6ccc2a14edbc7a02dc1ae2..2025f5c6f42c5b3312f0907b4f0deaaa3c6cefb9 100644
--- a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php
+++ b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php
@@ -5,6 +5,9 @@
  */
 namespace Magento\Framework\Module\Output;
 
+/**
+ * @deprecated
+ */
 interface ConfigInterface
 {
     /**
diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php
index f8fdc438b940cd5dbf604056c26f6727d03c990e..284367c84a721e3ce0202e9ab1f180c0eba96473 100644
--- a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php
+++ b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php
@@ -5,106 +5,60 @@
  */
 namespace Magento\Framework\Module\Test\Unit;
 
-use Magento\Framework\Module\Plugin\DbStatusValidator;
+use Magento\Framework\Module\Manager;
+use Magento\Framework\Module\ModuleListInterface;
+use Magento\Framework\Module\Output\ConfigInterface;
+use PHPUnit_Framework_MockObject_MockObject as Mock;
 
 class ManagerTest extends \PHPUnit_Framework_TestCase
 {
     /**
      * XPath in the configuration of a module output flag
+     * @deprecated
      */
     const XML_PATH_OUTPUT_ENABLED = 'custom/is_module_output_enabled';
 
     /**
-     * @var \Magento\Framework\Module\Manager
+     * @var Manager
      */
-    private $_model;
+    private $model;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var ModuleListInterface|Mock
      */
-    private $_moduleList;
+    private $moduleList;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var ConfigInterface|Mock
+     * @deprecated
      */
-    private $_outputConfig;
+    private $outputConfig;
 
     protected function setUp()
     {
-        $this->_moduleList = $this->getMockForAbstractClass(\Magento\Framework\Module\ModuleListInterface::class);
-        $this->_moduleList->expects($this->any())
-            ->method('getOne')
-            ->will($this->returnValueMap([
-                ['Module_One', ['name' => 'One_Module', 'setup_version' => '1']],
-                ['Module_Two', ['name' => 'Two_Module', 'setup_version' => '2']],
-                ['Module_Three', ['name' => 'Two_Three']],
-            ]));
-        $this->_outputConfig = $this->getMockForAbstractClass(\Magento\Framework\Module\Output\ConfigInterface::class);
-        $this->_model = new \Magento\Framework\Module\Manager(
-            $this->_outputConfig,
-            $this->_moduleList,
-            [
-                'Module_Two' => self::XML_PATH_OUTPUT_ENABLED,
-            ]
+        $this->moduleList = $this->getMockBuilder(ModuleListInterface::class)
+            ->getMockForAbstractClass();
+        $this->outputConfig = $this->getMockBuilder(ConfigInterface::class)
+            ->getMockForAbstractClass();
+
+        $this->model = new Manager(
+            $this->outputConfig,
+            $this->moduleList
         );
     }
 
     public function testIsEnabled()
     {
-        $this->_moduleList->expects($this->exactly(2))->method('has')->will($this->returnValueMap([
-            ['Module_Exists', true],
-            ['Module_NotExists', false],
-        ]));
-        $this->assertTrue($this->_model->isEnabled('Module_Exists'));
-        $this->assertFalse($this->_model->isEnabled('Module_NotExists'));
-    }
-
-    public function testIsOutputEnabledReturnsFalseForDisabledModule()
-    {
-        $this->_outputConfig->expects($this->any())->method('isSetFlag')->will($this->returnValue(true));
-        $this->assertFalse($this->_model->isOutputEnabled('Disabled_Module'));
-    }
-
-    /**
-     * @param bool $configValue
-     * @param bool $expectedResult
-     * @dataProvider isOutputEnabledGenericConfigPathDataProvider
-     */
-    public function testIsOutputEnabledGenericConfigPath($configValue, $expectedResult)
-    {
-        $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true));
-        $this->_outputConfig->expects($this->once())
-            ->method('isEnabled')
-            ->with('Module_One')
-            ->will($this->returnValue($configValue));
-        $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_One'));
-    }
+        $this->moduleList->expects($this->exactly(2))
+            ->method('has')
+            ->willReturnMap(
+                [
+                    ['Module_Exists', true],
+                    ['Module_NotExists', false],
+                ]
+            );
 
-    public function isOutputEnabledGenericConfigPathDataProvider()
-    {
-        return ['output disabled' => [true, false], 'output enabled' => [false, true]];
-    }
-
-    /**
-     * @param bool $configValue
-     * @param bool $expectedResult
-     * @dataProvider isOutputEnabledCustomConfigPathDataProvider
-     */
-    public function testIsOutputEnabledCustomConfigPath($configValue, $expectedResult)
-    {
-        $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true));
-        $this->_outputConfig->expects($this->at(0))
-            ->method('isSetFlag')
-            ->with(self::XML_PATH_OUTPUT_ENABLED)
-            ->will($this->returnValue($configValue));
-        $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_Two'));
-    }
-
-    public function isOutputEnabledCustomConfigPathDataProvider()
-    {
-        return [
-            'path literal, output disabled' => [false, false],
-            'path literal, output enabled'  => [true, true],
-        ];
+        $this->assertTrue($this->model->isEnabled('Module_Exists'));
+        $this->assertFalse($this->model->isEnabled('Module_NotExists'));
     }
 }
diff --git a/lib/internal/Magento/Framework/Setup/FilePermissions.php b/lib/internal/Magento/Framework/Setup/FilePermissions.php
index 1ca91b57e2e90ab38afcfecd53dee6f7268ccac6..4cc4a1852f0d725e324e4d18a5a53d7bf2f458a3 100644
--- a/lib/internal/Magento/Framework/Setup/FilePermissions.php
+++ b/lib/internal/Magento/Framework/Setup/FilePermissions.php
@@ -130,7 +130,7 @@ class FilePermissions
     }
 
     /**
-     * Check all sub-directories and files except for var/generation and var/di
+     * Check all sub-directories and files except for generated/code and generated/metadata
      *
      * @param string $directory
      * @return bool
diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
index bebeb7917f5010e768ac6b10ce4ca139de98900d..ee0b51b4b71d0dccae94956fcc44593340bbf992 100644
--- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
+++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
@@ -647,12 +647,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl
     public function toHtml()
     {
         $this->_eventManager->dispatch('view_block_abstract_to_html_before', ['block' => $this]);
-        if ($this->_scopeConfig->getValue(
-            'advanced/modules_disable_output/' . $this->getModuleName(),
-            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-        )) {
-            return '';
-        }
+        $this->getModuleName();
 
         $html = $this->_loadCache();
         if ($html === false) {
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
index b6846b52972e8b12287f727e56866c5452d04dcf..b6fde2c0f6a7fe465a82b47108dd9d9dd57b99a8 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
@@ -3,9 +3,6 @@
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
-// @codingStandardsIgnoreFile
-
 namespace Magento\Framework\View\Test\Unit\Element;
 
 use Magento\Framework\View\Element\AbstractBlock;
@@ -207,13 +204,12 @@ class AbstractBlockTest extends \PHPUnit_Framework_TestCase
         $moduleName = 'Test';
         $this->block->setData('module_name', $moduleName);
 
-        $this->eventManagerMock->expects($this->any())
+        $this->eventManagerMock->expects($this->exactly(2))
             ->method('dispatch')
-            ->with('view_block_abstract_to_html_before', ['block' => $this->block]);
-        $this->scopeConfigMock->expects($this->once())
-            ->method('getValue')
-            ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE)
-            ->willReturn(true);
+            ->willReturnMap([
+                ['view_block_abstract_to_html_before', ['block' => $this->block]],
+                ['view_block_abstract_to_html_after', ['block' => $this->block]],
+            ]);
 
         $this->assertSame('', $this->block->toHtml());
     }
@@ -246,10 +242,6 @@ class AbstractBlockTest extends \PHPUnit_Framework_TestCase
 
         $this->eventManagerMock->expects($expectsDispatchEvent)
             ->method('dispatch');
-        $this->scopeConfigMock->expects($this->once())
-            ->method('getValue')
-            ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE)
-            ->willReturn(false);
         $this->cacheStateMock->expects($this->any())
             ->method('isEnabled')
             ->with(AbstractBlock::CACHE_GROUP)
diff --git a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php
index 4734b99f19247e8763aa29ee0c19f6ae8fdce129..e652bb4d1183481b7c66230eacd7a0013bef3492 100644
--- a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php
+++ b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php
@@ -17,7 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 class UpgradeCommand extends AbstractSetupCommand
 {
     /**
-     * Option to skip deletion of var/generation directory
+     * Option to skip deletion of generated/code directory
      */
     const INPUT_KEY_KEEP_GENERATED = 'keep-generated';
 
diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php
index df32f9b018a0f2bbb8103ae9556c8863d17ff286..e986749ee6efb04a26aa73ddbd59fb933cc003a2 100644
--- a/setup/src/Magento/Setup/Console/CompilerPreparation.php
+++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php
@@ -1,49 +1,62 @@
 <?php
-/***
+/**
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
 namespace Magento\Setup\Console;
 
-
 use Magento\Framework\App\Bootstrap;
 use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Console\GenerationDirectoryAccess;
+use Magento\Framework\Exception\FileSystemException;
 use Magento\Framework\Filesystem\Driver\File;
+use Magento\Framework\Phrase;
 use Magento\Setup\Console\Command\DiCompileCommand;
 use Magento\Setup\Mvc\Bootstrap\InitParamListener;
 use Symfony\Component\Console\Input\ArgvInput;
+use Zend\ServiceManager\ServiceManager;
 
 class CompilerPreparation
 {
-    /** @var \Zend\ServiceManager\ServiceManager */
+    /**
+     * @var ServiceManager
+     */
     private $serviceManager;
 
-    /** @var ArgvInput */
+    /**
+     * @var ArgvInput
+     */
     private $input;
 
-    /** @var File */
+    /**
+     * @var File
+     */
     private $filesystemDriver;
 
     /**
-     * @param \Zend\ServiceManager\ServiceManager $serviceManager
+     * @var GenerationDirectoryAccess
+     */
+    private $generationDirectoryAccess;
+
+    /**
+     * @param ServiceManager $serviceManager
      * @param ArgvInput $input
      * @param File $filesystemDriver
      */
     public function __construct(
-        \Zend\ServiceManager\ServiceManager $serviceManager,
-        \Symfony\Component\Console\Input\ArgvInput $input,
-        \Magento\Framework\Filesystem\Driver\File $filesystemDriver
+        ServiceManager $serviceManager,
+        ArgvInput $input,
+        File $filesystemDriver
     ) {
-        $this->serviceManager   = $serviceManager;
-        $this->input            = $input;
+        $this->serviceManager = $serviceManager;
+        $this->input = $input;
         $this->filesystemDriver = $filesystemDriver;
     }
 
     /**
-     * Determine whether a CLI command is for compilation, and if so, clear the directory
+     * Determine whether a CLI command is for compilation, and if so, clear the directory.
      *
-     * @throws \Magento\Framework\Exception\FileSystemException
+     * @throws FileSystemException if generation directory is read-only
      * @return void
      */
     public function handleCompilerEnvironment()
@@ -63,10 +76,30 @@ class CompilerPreparation
         $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATION);
         $compileDirList[] = $directoryList->getPath(DirectoryList::DI);
 
+        if (!$this->getGenerationDirectoryAccess()->check()) {
+            throw new FileSystemException(
+                new Phrase('Generation directory can not be written.')
+            );
+        }
+
         foreach ($compileDirList as $compileDir) {
             if ($this->filesystemDriver->isExists($compileDir)) {
                 $this->filesystemDriver->deleteDirectory($compileDir);
             }
         }
     }
+
+    /**
+     * Retrieves generation directory access checker.
+     *
+     * @return GenerationDirectoryAccess the generation directory access checker
+     */
+    private function getGenerationDirectoryAccess()
+    {
+        if (null === $this->generationDirectoryAccess) {
+            $this->generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager);
+        }
+
+        return $this->generationDirectoryAccess;
+    }
 }
diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php
index 0918ee66fbb684e5553a857cda7e5807c417d6b8..088b40aa4decbf24fb11ab65dca17c7ca726e445 100644
--- a/setup/src/Magento/Setup/Model/Installer.php
+++ b/setup/src/Magento/Setup/Model/Installer.php
@@ -1262,7 +1262,7 @@ class Installer
     }
 
     /**
-     * Clear var/generation and reset object manager
+     * Clear generated/code and reset object manager
      *
      * @return void
      */
diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php
index 8a415dd65e43f4221abfd90d414dcc44fab617f3..5480a5953ce583fb50b27f6bbcd872acdcfdf0f7 100644
--- a/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php
+++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php
@@ -6,6 +6,7 @@
 namespace Magento\Setup\Module\Di\Code\Reader\Decorator;
 
 use Magento\Setup\Module\Di\Compiler\Log\Log;
+use Magento\Framework\App\Filesystem\DirectoryList;
 
 /**
  * Class Interceptions
@@ -71,7 +72,8 @@ class Interceptions implements \Magento\Setup\Module\Di\Code\Reader\ClassesScann
         $nameList = [];
         foreach ($this->classesScanner->getList($path) as $className) {
             try {
-                if (!strpos($path, 'generation')) { // validate all classes except classes in var/generation dir
+                // validate all classes except classes in generated/code dir
+                if (!strpos($path, DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH])) {
                     $this->validator->validate($className);
                 }
                 $nameList[] = $className;
diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php
index 813cb5dccc98f7ac7d34b0fdc28ff7769070bbaf..93118211062eddac065cdfe4b75bcb8f310421f3 100644
--- a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php
+++ b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php
@@ -1,48 +1,69 @@
 <?php
-/***
+/**
  * Copyright © 2013-2017 Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
 namespace Magento\Setup\Test\Unit\Console;
 
-
+use Magento\Framework\Console\GenerationDirectoryAccess;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 use Magento\Setup\Console\Command\DiCompileCommand;
 use Magento\Setup\Mvc\Bootstrap\InitParamListener;
+use Magento\Framework\Filesystem\Driver\File;
 use Symfony\Component\Console\Input\ArgvInput;
+use Zend\ServiceManager\ServiceManager;
+use Magento\Setup\Console\CompilerPreparation;
+use PHPUnit_Framework_MockObject_MockObject as Mock;
 
 class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
 {
-    /** @var \Magento\Setup\Console\CompilerPreparation */
+    /**
+     * @var CompilerPreparation|Mock
+     */
     private $model;
 
-    /** @var \Zend\ServiceManager\ServiceManager | \PHPUnit_Framework_MockObject_MockObject */
+    /**
+     * @var ServiceManager|Mock
+     */
     private $serviceManagerMock;
 
-    /** @var \Symfony\Component\Console\Input\ArgvInput | \PHPUnit_Framework_MockObject_MockObject */
+    /**
+     * @var ArgvInput|Mock
+     */
     private $inputMock;
 
-    /** @var \Magento\Framework\Filesystem\Driver\File | \PHPUnit_Framework_MockObject_MockObject */
+    /**
+     * @var File|Mock
+     */
     private $filesystemDriverMock;
 
+    /**
+     * @var GenerationDirectoryAccess|Mock
+     */
+    private $generationDirectoryAccessMock;
+
     public function setUp()
     {
-        $this->serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class)
+        $this->serviceManagerMock = $this->getMockBuilder(ServiceManager::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->inputMock = $this->getMockBuilder(ArgvInput::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->inputMock = $this->getMockBuilder(\Symfony\Component\Console\Input\ArgvInput::class)
+        $this->filesystemDriverMock = $this->getMockBuilder(File::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->filesystemDriverMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Driver\File::class)
+        $this->generationDirectoryAccessMock = $this->getMockBuilder(GenerationDirectoryAccess::class)
             ->disableOriginalConstructor()
             ->getMock();
+
         $this->model = (new ObjectManager($this))->getObject(
-            \Magento\Setup\Console\CompilerPreparation::class,
+            CompilerPreparation::class,
             [
                 'serviceManager' => $this->serviceManagerMock,
                 'input' => $this->inputMock,
-                'filesystemDriver' => $this->filesystemDriverMock
+                'filesystemDriver' => $this->filesystemDriverMock,
+                'generationDirectoryAccess' => $this->generationDirectoryAccessMock,
             ]
         );
     }
@@ -56,21 +77,31 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
      */
     public function testClearGenerationDirWhenNeeded($commandName, $isCompileCommand, $isHelpOption, $dirExists = false)
     {
-        $this->inputMock->expects($this->once())->method('getFirstArgument')->willReturn($commandName);
+        $this->inputMock->expects($this->once())
+            ->method('getFirstArgument')
+            ->willReturn($commandName);
         $this->inputMock->expects($this->atLeastOnce())
             ->method('hasParameterOption')
-            ->with(
-                $this->logicalOr('--help', '-h')
-            )->willReturn($isHelpOption);
+            ->with($this->logicalOr('--help', '-h'))
+            ->willReturn($isHelpOption);
+
         if ($isCompileCommand && !$isHelpOption) {
             $this->filesystemDriverMock->expects($this->exactly(2))
                 ->method('isExists')
                 ->willReturn($dirExists);
-            $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2))->method('deleteDirectory');
+            $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2))
+                ->method('deleteDirectory');
         } else {
-            $this->filesystemDriverMock->expects($this->never())->method('isExists');
-            $this->filesystemDriverMock->expects($this->never())->method('deleteDirectory');
+            $this->filesystemDriverMock->expects($this->never())
+                ->method('isExists');
+            $this->filesystemDriverMock->expects($this->never())
+                ->method('deleteDirectory');
         }
+
+        $this->generationDirectoryAccessMock->expects($this->any())
+            ->method('check')
+            ->willReturn(true);
+
         $this->model->handleCompilerEnvironment();
     }
 
@@ -108,10 +139,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
         $customGenerationDirectory = '/custom/generated/code/directory';
         $defaultDiDirectory = '/custom/di/directory';
         $mageInitParams = ['MAGE_DIRS' => ['generation' => ['path' => $customGenerationDirectory]]];
-
-        $this->inputMock->expects($this->once())
-            ->method('getFirstArgument')
-            ->willReturn(DiCompileCommand::NAME);
         $dirValueMap = [
             [
                 $customGenerationDirectory,
@@ -122,16 +149,24 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
                 true
             ]
         ];
-        // Filesystem mock
-        $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true);
+
+        $this->inputMock->expects($this->once())
+            ->method('getFirstArgument')
+            ->willReturn(DiCompileCommand::NAME);
+        $this->filesystemDriverMock->expects($this->exactly(2))
+            ->method('isExists')
+            ->willReturn(true);
         $this->filesystemDriverMock->expects($this->exactly(2))
             ->method('deleteDirectory')
-            ->will($this->returnValueMap($dirValueMap));
-
+            ->willReturnMap($dirValueMap);
         $this->serviceManagerMock->expects($this->once())
             ->method('get')
             ->with(InitParamListener::BOOTSTRAP_PARAM)
             ->willReturn($mageInitParams);
+        $this->generationDirectoryAccessMock->expects($this->once())
+            ->method('check')
+            ->willReturn(true);
+
         $this->model->handleCompilerEnvironment();
     }
 
@@ -139,10 +174,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
     {
         $customGenerationDirectory = '/custom/generated/code/directory';
         $customDiDirectory = '/custom/di/directory';
-        
-        $this->inputMock->expects($this->once())
-            ->method('getFirstArgument')
-            ->willReturn(DiCompileCommand::NAME);
         $dirResultMap = [
             [
                 $this->logicalNot($this->equalTo($customGenerationDirectory)),
@@ -154,10 +185,18 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase
             ]
         ];
 
-        $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true);
+        $this->inputMock->expects($this->once())
+            ->method('getFirstArgument')
+            ->willReturn(DiCompileCommand::NAME);
+        $this->filesystemDriverMock->expects($this->exactly(2))
+            ->method('isExists')
+            ->willReturn(true);
         $this->filesystemDriverMock->expects($this->exactly(2))
             ->method('deleteDirectory')
-            ->will($this->returnValueMap($dirResultMap));
+            ->willReturnMap($dirResultMap);
+        $this->generationDirectoryAccessMock->expects($this->once())
+            ->method('check')
+            ->willReturn(true);
 
         $this->model->handleCompilerEnvironment();
     }
diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php
index b34322ec9992a650f87ad973f94bb07a27c858fb..0ccc4d9f9b35fe82e3775d2934c27dfecb10c382 100644
--- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php
+++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php
@@ -66,7 +66,7 @@ class DirectoryTest extends \PHPUnit_Framework_TestCase
             $this->classReaderMock,
             $this->classesScanner,
             $this->validatorMock,
-            '/var/generation'
+            '/generated/code'
         );
     }
 
@@ -111,7 +111,7 @@ class DirectoryTest extends \PHPUnit_Framework_TestCase
 
     public function testGetListNoValidation()
     {
-        $path = '/var/generation';
+        $path = '/generated/code';
 
         $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2'];
 
diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php
index 0591b11b9628c0117ede3b88201722ed6d86da2a..733ba0b85866e38d24f17e6b2d031e75bab17ce3 100644
--- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php
+++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php
@@ -97,7 +97,7 @@ class InterceptionsTest extends \PHPUnit_Framework_TestCase
 
     public function testGetListNoValidation()
     {
-        $path = '/var/generation';
+        $path = '/generated/code';
 
         $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2'];