├── src ├── Transform.UnitTests │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Program_Tests.cs │ └── Transform.UnitTests.csproj ├── Sitecore.Configuration.Roles │ ├── packages.config │ ├── Sitecore.Configuration.Roles.nuspec │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── BooleanLogicParser │ │ ├── Tokens.cs │ │ ├── Parser.cs │ │ └── Tokenizer.cs │ ├── SC.Sitecore.Configuration.Roles.nuspec │ ├── RoleConfigReader.cs │ ├── Sitecore.Configuration.Roles.csproj │ ├── RoleConfigurationHelper.cs │ └── RoleXmlPatchHelper.cs ├── Sitecore.Configuration.Roles.UnitTests │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RoleConfigurationHelper_ValidateRoles.cs │ ├── Sitecore.Configuration.Roles.UnitTests.csproj │ └── RoleXmlPatchHelperTests.cs ├── Transform │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Transform.csproj │ └── Program.cs └── Sitecore.Configuration.Roles.sln ├── .gitignore └── readme.md /src/Transform.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pubxml 2 | *.suo 3 | *.user 4 | src/.vs/ 5 | src/Sitecore.Configuration.Roles.UnitTests/bin/ 6 | src/Sitecore.Configuration.Roles.UnitTests/obj/ 7 | src/Sitecore.Configuration.Roles/bin/ 8 | src/Sitecore.Configuration.Roles/obj/ 9 | src/Website/bin/ 10 | src/Website/obj/ 11 | src/packages/ 12 | src/TestResults/ 13 | src/Transform.UnitTests/bin/ 14 | src/Transform.UnitTests/obj/ 15 | src/Transform/bin/ 16 | src/Transform/obj/ 17 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Transform/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | 7 | [assembly: AssemblyTitle("Transform")] 8 | [assembly: AssemblyProduct("Sitecore.Configuration.Roles")] 9 | 10 | [assembly: AssemblyVersion("1.3.0.0")] 11 | [assembly: AssemblyFileVersion("1.3.0.0")] 12 | 13 | [assembly: InternalsVisibleTo("Transform.UnitTests")] -------------------------------------------------------------------------------- /src/Transform.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Transform.UnitTests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Transform.UnitTests")] 10 | [assembly: AssemblyCopyright("Copyright © 2017")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("9f9f65c7-9816-4e51-8a0b-4f5509682708")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/Sitecore.Configuration.Roles.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sitecore.Configuration.Roles 5 | 1.1.0 6 | Alen Pelin 7 | Sitecore 8 | false 9 | Configuration Role Engine for Sitecore. 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Sitecore.Configuration.Roles")] 6 | [assembly: AssemblyProduct("Sitecore.Configuration.Roles")] 7 | [assembly: AssemblyDescription("Extends default configuration engine with role:require attribute.")] 8 | 9 | [assembly: AssemblyCompany("Sitecore")] 10 | [assembly: AssemblyCopyright("Copyright © Sitecore 2015")] 11 | [assembly: ComVisible(false)] 12 | 13 | [assembly: InternalsVisibleTo("Sitecore.Configuration.Roles.UnitTests")] 14 | 15 | [assembly: AssemblyVersion("1.3.0.0")] 16 | [assembly: AssemblyFileVersion("1.3.0.0")] 17 | [assembly: AssemblyInformationalVersion("1.2 rev. 170405")] 18 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Sitecore.Configuration.Roles.UnitTests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Sitecore.Configuration.Roles.UnitTests")] 10 | [assembly: AssemblyCopyright("Copyright © 2017")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("66c9dfb2-5c20-4dff-9062-50d8a6de50ff")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/BooleanLogicParser/Tokens.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles.BooleanLogic 2 | { 3 | internal class OperandToken : Token 4 | { 5 | } 6 | 7 | internal class OrToken : OperandToken 8 | { 9 | } 10 | 11 | internal class AndToken : OperandToken 12 | { 13 | } 14 | 15 | internal class BooleanValueToken : Token 16 | { 17 | } 18 | 19 | internal class FalseToken : BooleanValueToken 20 | { 21 | } 22 | 23 | internal class TrueToken : BooleanValueToken 24 | { 25 | } 26 | 27 | internal class ParenthesisToken : Token 28 | { 29 | } 30 | 31 | internal class ClosedParenthesisToken : ParenthesisToken 32 | { 33 | } 34 | 35 | internal class OpenParenthesisToken : ParenthesisToken 36 | { 37 | } 38 | 39 | internal class NegationToken : Token 40 | { 41 | } 42 | 43 | internal abstract class Token 44 | { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/SC.Sitecore.Configuration.Roles.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SC.Sitecore.Configuration.Roles 5 | 1.0.0 6 | Alen Pelin 7 | Sitecore 8 | false 9 | Configuration Role Engine for Sitecore. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.UnitTests/RoleConfigurationHelper_ValidateRoles.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles.UnitTests 2 | { 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using FluentAssertions; 5 | 6 | [TestClass] 7 | public class RoleConfigurationHelper_ValidateRoles 8 | { 9 | [TestMethod] 10 | public void Authoring() 11 | { 12 | RoleConfigurationHelper.ValidateRoles(new[] { "authoring" }) 13 | .Should().BeNull(); 14 | } 15 | 16 | [TestMethod] 17 | public void Processing() 18 | { 19 | RoleConfigurationHelper.ValidateRoles(new[] { "processing" }) 20 | .Should().BeNull(); 21 | } 22 | 23 | [TestMethod] 24 | public void Reporting() 25 | { 26 | RoleConfigurationHelper.ValidateRoles(new[] { "reporting" }) 27 | .Should().BeNull(); 28 | } 29 | 30 | [TestMethod] 31 | public void Delivery() 32 | { 33 | RoleConfigurationHelper.ValidateRoles(new[] { "delivery" }) 34 | .Should().BeNull(); 35 | } 36 | 37 | [TestMethod] 38 | public void AuthoringDelivery() 39 | { 40 | RoleConfigurationHelper.ValidateRoles(new[] { "authoring", "delivery" }) 41 | .Should().BeNull(); 42 | } 43 | 44 | [TestMethod] 45 | public void AuthoringDeliveryOne() 46 | { 47 | RoleConfigurationHelper.ValidateRoles(new[] { "authoring", "delivery" }) 48 | .Should().BeNull(); 49 | } 50 | 51 | [TestMethod] 52 | public void AllInOneDeliveryOne() 53 | { 54 | RoleConfigurationHelper.ValidateRoles(new[] { "authoring", "processing", "reporting", "delivery" }) 55 | .Should().BeNull(); 56 | } 57 | 58 | [TestMethod] 59 | public void AuthoringProcessingReporting() 60 | { 61 | RoleConfigurationHelper.ValidateRoles(new[] { "authoring", "processing", "reporting" }) 62 | .Should().BeNull(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/BooleanLogicParser/Parser.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles.BooleanLogic 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | // Expression := [ "!" ] { } ... 7 | // Boolean := | | "(" ")" 8 | // BooleanOperator := "And" | "Or" 9 | // BooleanConstant := "True" | "False" 10 | internal class Parser 11 | { 12 | private readonly IEnumerator _tokens; 13 | 14 | internal Parser(IEnumerable tokens) 15 | { 16 | _tokens = tokens.GetEnumerator(); 17 | _tokens.MoveNext(); 18 | } 19 | 20 | internal bool Parse() 21 | { 22 | while (_tokens.Current != null) 23 | { 24 | var isNegated = _tokens.Current is NegationToken; 25 | if (isNegated) 26 | { 27 | _tokens.MoveNext(); 28 | } 29 | 30 | var boolean = ParseBoolean(); 31 | if (isNegated) 32 | { 33 | boolean = !boolean; 34 | } 35 | 36 | while (_tokens.Current is OperandToken) 37 | { 38 | var operand = _tokens.Current; 39 | if (!_tokens.MoveNext()) 40 | { 41 | throw new Exception("Missing expression after operand"); 42 | } 43 | 44 | var nextBoolean = ParseBoolean(); 45 | 46 | if (operand is AndToken) 47 | { 48 | boolean = boolean && nextBoolean; 49 | } 50 | else 51 | { 52 | boolean = boolean || nextBoolean; 53 | } 54 | } 55 | 56 | return boolean; 57 | } 58 | 59 | throw new Exception("Empty expression"); 60 | } 61 | 62 | private bool ParseBoolean() 63 | { 64 | if (_tokens.Current is BooleanValueToken) 65 | { 66 | var current = _tokens.Current; 67 | _tokens.MoveNext(); 68 | 69 | return current is TrueToken; 70 | } 71 | 72 | if (_tokens.Current is OpenParenthesisToken) 73 | { 74 | _tokens.MoveNext(); 75 | 76 | var expInPars = Parse(); 77 | 78 | if (_tokens.Current is ClosedParenthesisToken) 79 | { 80 | _tokens.MoveNext(); 81 | 82 | return expInPars; 83 | } 84 | 85 | throw new Exception("Expecting Closing Parenthesis"); 86 | } 87 | 88 | if (_tokens.Current is ClosedParenthesisToken) 89 | { 90 | throw new Exception("Unexpected Closed Parenthesis"); 91 | } 92 | 93 | // since its not a BooleanConstant or Expression in parenthesis, it must be a expression again 94 | var ret = Parse(); 95 | 96 | return ret; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/Transform/Transform.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D0C52BE8-194D-400C-BC15-6600315BA39E} 8 | Exe 9 | Transform 10 | Transform 11 | v4.5 12 | 512 13 | 14 | 15 | AnyCPU 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | AnyCPU 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | BooleanLogicParser\Parser.cs 46 | 47 | 48 | BooleanLogicParser\Tokenizer.cs 49 | 50 | 51 | BooleanLogicParser\Tokens.cs 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/RoleConfigReader.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.IO; 6 | using System.Xml; 7 | using Sitecore.Diagnostics; 8 | using Sitecore.Xml.Patch; 9 | 10 | [UsedImplicitly] 11 | public class RoleConfigReader : ConfigReader 12 | { 13 | [NotNull] 14 | private readonly string[] IncludeOverride = ReadIncludeOverride(); 15 | 16 | private readonly RoleConfigurationHelper RoleConfigurationHelper; 17 | 18 | private readonly XmlPatcher Patcher; 19 | 20 | public RoleConfigReader() 21 | { 22 | RoleConfigurationHelper = new RoleConfigurationHelper(); 23 | Patcher = new XmlPatcher("http://www.sitecore.net/xmlconfig/set/", "http://www.sitecore.net/xmlconfig/", new RoleXmlPatchHelper(RoleConfigurationHelper)); 24 | } 25 | 26 | protected override void ExpandIncludeFiles([NotNull] XmlNode rootNode, [NotNull] Hashtable cycleDetector) 27 | { 28 | Assert.ArgumentNotNull(rootNode, "rootNode"); 29 | Assert.ArgumentNotNull(cycleDetector, "cycleDetector"); 30 | 31 | RoleConfigurationHelper.LoadAppSetting(); 32 | 33 | base.ExpandIncludeFiles(rootNode, cycleDetector); 34 | } 35 | 36 | protected override void ReplaceGlobalVariables([NotNull] XmlNode rootNode) 37 | { 38 | Assert.ArgumentNotNull(rootNode, "rootNode"); 39 | 40 | base.ReplaceGlobalVariables(rootNode); 41 | 42 | RoleConfigurationHelper.Validate(); 43 | } 44 | 45 | [NotNull] 46 | protected override ConfigPatcher GetConfigPatcher([NotNull] XmlNode element) 47 | { 48 | Assert.ArgumentNotNull(element, "element"); 49 | 50 | return new ConfigPatcher(element, this.Patcher); 51 | } 52 | 53 | protected override void LoadAutoIncludeFiles(XmlNode element) 54 | { 55 | Assert.ArgumentNotNull(element, "element"); 56 | 57 | if (IncludeOverride.Length == 0) 58 | { 59 | base.LoadAutoIncludeFiles(element); 60 | 61 | return; 62 | } 63 | 64 | var configPatcher = GetConfigPatcher(element); 65 | LoadAutoIncludeFiles(configPatcher, MainUtil.MapPath("/App_Config/Sitecore/Components")); 66 | 67 | foreach (var path in IncludeOverride) 68 | { 69 | Assert.IsTrue(Directory.Exists(path), "The include:override setting points to non-existing folder: {0}", path); 70 | Assert.IsTrue(Path.IsPathRooted(path), "The include:override setting points to non-rooted path: {0}", path); 71 | 72 | LoadAutoIncludeFiles(configPatcher, path); 73 | } 74 | } 75 | 76 | [NotNull] 77 | private static string[] ReadIncludeOverride() 78 | { 79 | var includeOverride = System.Configuration.ConfigurationManager.AppSettings["include:override"]; 80 | if (string.IsNullOrEmpty(includeOverride)) 81 | { 82 | return new string[0]; 83 | } 84 | 85 | return includeOverride.Split(";|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.6 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitecore.Configuration.Roles", "Sitecore.Configuration.Roles\Sitecore.Configuration.Roles.csproj", "{6A31C120-8BA2-42A3-9C9B-4D40B626EC3A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitecore.Configuration.Roles.UnitTests", "Sitecore.Configuration.Roles.UnitTests\Sitecore.Configuration.Roles.UnitTests.csproj", "{66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transform", "Transform\Transform.csproj", "{D0C52BE8-194D-400C-BC15-6600315BA39E}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{E882ABF3-83DC-447A-89B7-F90AC957F08A}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transform.UnitTests", "Transform.UnitTests\Transform.UnitTests.csproj", "{9F9F65C7-9816-4E51-8A0B-4F5509682708}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {6A31C120-8BA2-42A3-9C9B-4D40B626EC3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {6A31C120-8BA2-42A3-9C9B-4D40B626EC3A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {6A31C120-8BA2-42A3-9C9B-4D40B626EC3A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {6A31C120-8BA2-42A3-9C9B-4D40B626EC3A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {D0C52BE8-194D-400C-BC15-6600315BA39E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D0C52BE8-194D-400C-BC15-6600315BA39E}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D0C52BE8-194D-400C-BC15-6600315BA39E}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D0C52BE8-194D-400C-BC15-6600315BA39E}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {9F9F65C7-9816-4E51-8A0B-4F5509682708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {9F9F65C7-9816-4E51-8A0B-4F5509682708}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {9F9F65C7-9816-4E51-8A0B-4F5509682708}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {9F9F65C7-9816-4E51-8A0B-4F5509682708}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {D0C52BE8-194D-400C-BC15-6600315BA39E} = {E882ABF3-83DC-447A-89B7-F90AC957F08A} 44 | {9F9F65C7-9816-4E51-8A0B-4F5509682708} = {E882ABF3-83DC-447A-89B7-F90AC957F08A} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/Sitecore.Configuration.Roles.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6A31C120-8BA2-42A3-9C9B-4D40B626EC3A} 8 | Library 9 | Properties 10 | Sitecore.Configuration.Roles 11 | Sitecore.Configuration.Roles 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\SC.Sitecore.Kernel.8.1.2\lib\Sitecore.Kernel.dll 37 | False 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 61 | 62 | 63 | 64 | 71 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/BooleanLogicParser/Tokenizer.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles.BooleanLogic 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | internal class Tokenizer 10 | { 11 | private readonly StringReader _reader; 12 | private string _text; 13 | private readonly string[] _trueAliases; 14 | 15 | internal Tokenizer(string text) 16 | { 17 | _text = text; 18 | _reader = new StringReader(text); 19 | _trueAliases = new string[0]; 20 | } 21 | 22 | internal Tokenizer(string text, params string[] trueAliases) 23 | { 24 | _text = text; 25 | _reader = new StringReader(text); 26 | _trueAliases = trueAliases; 27 | } 28 | 29 | internal IEnumerable Tokenize() 30 | { 31 | var tokens = new List(); 32 | while (_reader.Peek() != -1) 33 | { 34 | while (char.IsWhiteSpace((char)_reader.Peek())) 35 | { 36 | _reader.Read(); 37 | } 38 | 39 | if (_reader.Peek() == -1) 40 | { 41 | break; 42 | } 43 | 44 | var c = (char)_reader.Peek(); 45 | switch (c) 46 | { 47 | case '&': 48 | tokens.Add(new AndToken()); 49 | _reader.Read(); 50 | break; 51 | 52 | case '|': 53 | tokens.Add(new OrToken()); 54 | _reader.Read(); 55 | break; 56 | 57 | case '!': 58 | tokens.Add(new NegationToken()); 59 | _reader.Read(); 60 | break; 61 | 62 | case '(': 63 | tokens.Add(new OpenParenthesisToken()); 64 | _reader.Read(); 65 | break; 66 | 67 | case ')': 68 | tokens.Add(new ClosedParenthesisToken()); 69 | _reader.Read(); 70 | break; 71 | 72 | default: 73 | if (IsValidTokenCharacter(c)) 74 | { 75 | var token = ParseKeyword(); 76 | tokens.Add(token); 77 | } 78 | else 79 | { 80 | var remainingText = _reader.ReadToEnd() ?? string.Empty; 81 | throw new Exception(string.Format("Unknown grammar found at position {0} : '{1}'", _text.Length - remainingText.Length, remainingText)); 82 | } 83 | break; 84 | } 85 | } 86 | 87 | return tokens; 88 | } 89 | 90 | private static bool IsValidTokenCharacter(char c) 91 | { 92 | return char.IsLetter(c) || char.IsDigit(c) || c == '-' || c == '_' || c == '.' || c == '/'; 93 | } 94 | 95 | private Token ParseKeyword() 96 | { 97 | var text = new StringBuilder(); 98 | while (IsValidTokenCharacter((char)_reader.Peek())) 99 | { 100 | text.Append((char)_reader.Read()); 101 | } 102 | 103 | var potentialKeyword = text.ToString().ToLower(); 104 | 105 | var aliases = _trueAliases.Length > 0; 106 | if (_trueAliases.Any(x => x.Equals(potentialKeyword, StringComparison.OrdinalIgnoreCase))) 107 | { 108 | return new TrueToken(); 109 | } 110 | 111 | switch (potentialKeyword) 112 | { 113 | case "true": 114 | return new TrueToken(); 115 | 116 | case "false": 117 | return new FalseToken(); 118 | 119 | case "and": 120 | return new AndToken(); 121 | 122 | case "or": 123 | return new OrToken(); 124 | 125 | default: 126 | if (aliases) 127 | { 128 | return new FalseToken(); 129 | } 130 | else 131 | { 132 | throw new Exception("Expected keyword (True, False, And, Or) but found " + potentialKeyword); 133 | } 134 | } 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/RoleConfigurationHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Configuration; 6 | using System.Linq; 7 | using System.Xml; 8 | using BooleanLogic; 9 | using Sitecore.Diagnostics; 10 | using Sitecore.Xml.Patch; 11 | 12 | /// 13 | /// The configuration roles helper. 14 | /// 15 | public class RoleConfigurationHelper 16 | { 17 | [CanBeNull] 18 | private string[] definedRoles; 19 | 20 | public RoleConfigurationHelper() 21 | { 22 | } 23 | 24 | internal RoleConfigurationHelper(string[] roles) 25 | { 26 | definedRoles = roles; 27 | } 28 | 29 | /// 30 | /// List of defined roles. 31 | /// 32 | /// 33 | /// The defined roles. 34 | /// 35 | [NotNull] 36 | public IEnumerable DefinedRoles 37 | { 38 | get 39 | { 40 | return (definedRoles ?? new string[0]).ToArray(); 41 | } 42 | } 43 | 44 | [CanBeNull] 45 | internal string DefinedRolesSource { get; private set; } 46 | 47 | [CanBeNull] 48 | internal string DefinedRolesErrorSource { get; private set; } 49 | 50 | [CanBeNull] 51 | private string DefinedRolesErrorMessage { get; set; } 52 | 53 | internal void LoadAppSetting() 54 | { 55 | var roleDefine = System.Configuration.ConfigurationManager.AppSettings["role:define"]; 56 | if (!string.IsNullOrEmpty(roleDefine)) 57 | { 58 | DefineRolesOnce(roleDefine, "web.config"); 59 | } 60 | } 61 | 62 | internal void Validate() 63 | { 64 | if (string.IsNullOrEmpty(DefinedRolesErrorSource) && string.IsNullOrEmpty(DefinedRolesErrorMessage)) 65 | { 66 | return; 67 | } 68 | 69 | throw new ConfigurationErrorsException(DefinedRolesErrorMessage, DefinedRolesErrorSource, 0); 70 | } 71 | 72 | internal bool ProcessRolesNamespace([NotNull] IXmlNode attribute) 73 | { 74 | Assert.ArgumentNotNull(attribute, "node"); 75 | Assert.ArgumentCondition(attribute.NodeType == XmlNodeType.Attribute, "attribute", "The attribute node is not an XmlNodeType.Attribute"); 76 | 77 | var name = attribute.LocalName; 78 | var value = attribute.Value; 79 | switch (name) 80 | { 81 | case "r": 82 | case "require": 83 | if (!string.IsNullOrEmpty(value)) 84 | { 85 | var tokens = new Tokenizer(value, DefinedRoles.ToArray()).Tokenize(); 86 | var ret = new Parser(tokens).Parse(); 87 | 88 | return ret; 89 | } 90 | 91 | break; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | private void DefineRolesOnce([NotNull] string value, [NotNull] IXmlNode node) 98 | { 99 | Assert.ArgumentNotNull(value, "value"); 100 | Assert.ArgumentNotNull(node, "node"); 101 | 102 | var source = (IXmlSource)node; 103 | var sourceName = source.SourceName; 104 | 105 | DefineRolesOnce(value, sourceName); 106 | } 107 | 108 | private void DefineRolesOnce([NotNull] string value, [NotNull] string sourceName) 109 | { 110 | Assert.ArgumentNotNull(value, "value"); 111 | Assert.ArgumentNotNull(sourceName, "sourceName"); 112 | 113 | if (definedRoles != null && DefinedRolesSource != sourceName) 114 | { 115 | DefinedRolesErrorSource = sourceName; 116 | DefinedRolesErrorMessage = string.Format( 117 | "Current set of roles defined in the \"{0}\" file was attempted to be modified in the \"{1}\" file. " + 118 | "This is not allowed to prevent unintended configuration changes. " + 119 | "If roles from both files are valid, they need to be merged into a single file.", 120 | DefinedRolesSource, 121 | DefinedRolesErrorSource); 122 | 123 | return; 124 | } 125 | 126 | var roles = value.Split("|,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries) 127 | .Select(x => x.ToLowerInvariant()) 128 | .Distinct() 129 | .ToList(); 130 | 131 | var error = ValidateRoles(roles); 132 | if (!string.IsNullOrEmpty(error)) 133 | { 134 | DefinedRolesErrorMessage = error; 135 | DefinedRolesErrorSource = sourceName; 136 | 137 | return; 138 | } 139 | 140 | definedRoles = roles.ToArray(); 141 | DefinedRolesSource = sourceName; 142 | } 143 | 144 | internal static string ValidateRoles(ICollection roles) 145 | { 146 | return null; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Transform.UnitTests/Program_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Transform.UnitTests 5 | { 6 | using System.Linq; 7 | using System.Xml; 8 | 9 | [TestClass] 10 | public class Program_Tests 11 | { 12 | [TestMethod] 13 | public void ParseRole() 14 | { 15 | // arrange 16 | var webConfig = ParseXml(@" 17 | 18 | 19 | 20 | 21 | "); 22 | 23 | // act 24 | var role = Program.ParseRole(webConfig); 25 | 26 | // assert 27 | Assert.AreEqual("abc", role); 28 | } 29 | 30 | [TestMethod] 31 | public void ProcessFile_Partial() 32 | { 33 | // arrange 34 | var xml = ParseXml(@" 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | "); 48 | 49 | var expected = ParseXml(@" 50 | 51 | 52 | 53 | 54 | 55 | 56 | "); 57 | 58 | // act 59 | var result = Program.ProcessFile(xml.DocumentElement, "Authoring".Split()); 60 | 61 | // assert 62 | CompareElements(expected.DocumentElement, xml.DocumentElement, "/configuration", 0); 63 | Assert.IsTrue(result); 64 | } 65 | 66 | [TestMethod] 67 | public void ProcessFile_Empty() 68 | { 69 | // arrange 70 | var xml = ParseXml(@" 71 | 72 | 73 | 74 | 75 | "); 76 | 77 | var expected = ParseXml(@" 78 | 79 | "); 80 | 81 | // act 82 | var result = Program.ProcessFile(xml.DocumentElement, "Authoring".Split()); 83 | 84 | // assert 85 | CompareElements(expected.DocumentElement, xml.DocumentElement, "/configuration", 0); 86 | Assert.IsFalse(result); 87 | } 88 | 89 | private void CompareElements(XmlElement expected, XmlElement actual, string path, int childNumber) 90 | { 91 | CompareAttributes(expected.Attributes, actual.Attributes, path, childNumber); 92 | for (var i = 0; i < Math.Max(expected.ChildNodes.Count, actual.ChildNodes.Count); ++i) 93 | { 94 | var exp = i < expected.ChildNodes.Count ? (XmlElement)expected.ChildNodes[i] : null; 95 | var act = i < actual.ChildNodes.Count ? (XmlElement)actual.ChildNodes[i] : null; 96 | if (exp == null) 97 | { 98 | if (act == null) 99 | { 100 | throw new NotImplementedException("This cannot be"); 101 | } 102 | 103 | Assert.Fail($"Unexpected xml element by path {path}[{childNumber}]: {act}"); 104 | } 105 | else if (act == null) 106 | { 107 | Assert.Fail($"Missing xml element by path {path}[{childNumber}]: {exp}"); 108 | } 109 | else 110 | { 111 | CompareElements(exp, act, path + "/" + exp.Name, i); 112 | } 113 | } 114 | } 115 | 116 | private void CompareAttributes(XmlAttributeCollection expected1, XmlAttributeCollection actual1, string path, int childNumber) 117 | { 118 | var expected = expected1.OfType().ToList(); 119 | var actual = actual1.OfType().ToList(); 120 | foreach (XmlAttribute exp in expected1) 121 | { 122 | expected.Remove(exp); 123 | var act = actual.FirstOrDefault(x => x.Name == exp.Name); 124 | Assert.IsNotNull(act, $"{path}[{childNumber}][@{exp.Name}]"); 125 | 126 | Assert.AreEqual(exp.Value, act.Value); 127 | actual.Remove(act); 128 | } 129 | 130 | foreach (var act in actual) 131 | { 132 | Assert.Fail($"Unexpected attr {path}[{childNumber}][@{act.Name}='{act.Value}']"); 133 | } 134 | } 135 | 136 | private static XmlDocument ParseXml(string xml) 137 | { 138 | var webConfig = new XmlDocument(); 139 | webConfig.LoadXml(xml.Replace("'", "\"").Trim()); 140 | return webConfig; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Transform.UnitTests/Transform.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9F9F65C7-9816-4E51-8A0B-4F5509682708} 8 | Library 9 | Properties 10 | Transform.UnitTests 11 | Transform.UnitTests 12 | v4.5 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 43 | 44 | 45 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {D0C52BE8-194D-400C-BC15-6600315BA39E} 61 | Transform 62 | 63 | 64 | 65 | 66 | 67 | 68 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.UnitTests/Sitecore.Configuration.Roles.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {66C9DFB2-5C20-4DFF-9062-50D8A6DE50FF} 8 | Library 9 | Properties 10 | Sitecore.Configuration.Roles.UnitTests 11 | Sitecore.Configuration.Roles.UnitTests 12 | v4.6.2 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | ..\packages\FluentAssertions.4.19.2\lib\net45\FluentAssertions.dll 43 | 44 | 45 | ..\packages\FluentAssertions.4.19.2\lib\net45\FluentAssertions.Core.dll 46 | 47 | 48 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 49 | 50 | 51 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 52 | 53 | 54 | ..\packages\SC.Sitecore.Kernel.8.1.2\lib\Sitecore.Kernel.dll 55 | True 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {6a31c120-8ba2-42a3-9c9b-4d40b626ec3a} 73 | Sitecore.Configuration.Roles 74 | 75 | 76 | 77 | 78 | 79 | 80 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/Transform/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Transform 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | using Sitecore.Configuration.Roles.BooleanLogic; 9 | 10 | public static class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | if (args.Length == 0) 15 | { 16 | Console.WriteLine("Transform.exe - the tool is part of Sitecore Configuration Roles 1.2"); 17 | Console.WriteLine(); 18 | Console.WriteLine("The only purpose is to convert roles-enabled configuration files and"); 19 | Console.WriteLine("downgrade them to regular configuration files: "); 20 | Console.WriteLine(" * update web.config file with stock config provider"); 21 | Console.WriteLine(" * update all include files by removing all sections that do not comply "); 22 | Console.WriteLine(" with roles specified in role:define setting in web.config file and all"); 23 | Console.WriteLine(" signs of configuration roles used."); 24 | Console.WriteLine(); 25 | Console.WriteLine("Usage: "); 26 | Console.WriteLine(" > Transform.exe []"); 27 | Console.WriteLine(" (if output folder omitted, creates Include_ folder instead"); 28 | 29 | return; 30 | } 31 | 32 | var filePath = GetWebConfigPath(args); 33 | var webConfig = ReadWebConfigFile(filePath); 34 | var role = ParseRole(webConfig); 35 | if (string.IsNullOrEmpty(role)) 36 | { 37 | throw new NotSupportedException("Cannot find child element of , or the value is empty"); 38 | } 39 | 40 | var timestamp = $"{DateTime.Now:yyyyMMdd-HHmmss}"; 41 | ChangeConfigProvider(webConfig, $"{filePath}_{timestamp}"); 42 | 43 | var folderPath = Path.GetDirectoryName(filePath); 44 | folderPath = Path.Combine(folderPath, "App_Config\\Include"); 45 | 46 | var outputDir = GetOutputDirectoryPath(args); 47 | outputDir = string.IsNullOrEmpty(outputDir) ? $"{folderPath}_{timestamp}" : Path.Combine(outputDir, "App_Config\\Include"); 48 | Directory.CreateDirectory(outputDir); 49 | 50 | var files = Directory.GetFiles(folderPath, "*.config", SearchOption.AllDirectories); 51 | foreach (var file in files) 52 | { 53 | ProcessFile(file, folderPath, outputDir, role.Split("|;,".ToCharArray())); 54 | } 55 | } 56 | 57 | private static void ProcessFile(string filePath, string sourceFolderPath, string outputFolderPath, string[] roles) 58 | { 59 | var relativePath = filePath.Substring(sourceFolderPath.Length).TrimStart("\\/".ToCharArray()); 60 | var newFilePath = Path.Combine(outputFolderPath, relativePath); 61 | Directory.CreateDirectory(Path.GetDirectoryName(newFilePath)); 62 | 63 | var outputStream = File.OpenWrite(newFilePath); 64 | try 65 | { 66 | var xml = new XmlDocument(); 67 | xml.Load(filePath); 68 | 69 | // do work 70 | if (!ProcessFile(xml.DocumentElement, roles)) 71 | { 72 | // delete (do not copy to output folder) include file if it does't have element 73 | return; 74 | } 75 | 76 | using (var writer = new XmlTextWriter(outputStream, Encoding.Unicode)) 77 | { 78 | xml.WriteTo(writer); 79 | } 80 | } 81 | finally 82 | { 83 | outputStream.Close(); 84 | } 85 | } 86 | 87 | internal static bool ProcessFile(XmlElement xml, string[] roles) 88 | { 89 | ProcessElement(xml, roles); 90 | StripNamespace(xml); 91 | 92 | return xml.SelectSingleNode("sitecore") != null; 93 | } 94 | 95 | private static void StripNamespace(XmlElement xml) 96 | { 97 | // remove namespace 98 | xml.Attributes 99 | .OfType() 100 | .Where(x => x.Prefix == "xmlns" && x.Value == "http://www.sitecore.net/xmlconfig/role/") 101 | .ToList() 102 | .ForEach(x => 103 | x.OwnerElement.Attributes.Remove(x)); 104 | 105 | foreach (var child in xml.ChildNodes.OfType()) 106 | { 107 | StripNamespace(child); 108 | } 109 | } 110 | 111 | private static void ProcessElement(XmlElement xml, string[] roles) 112 | { 113 | var require = xml.Attributes["require", "http://www.sitecore.net/xmlconfig/role/"]; 114 | if (require != null) 115 | { 116 | var tokens = new Tokenizer(require.Value, roles).Tokenize(); 117 | if (!new Parser(tokens).Parse()) 118 | { 119 | xml.ParentNode.RemoveChild(xml); 120 | return; 121 | } 122 | } 123 | 124 | if (require != null) 125 | { 126 | xml.Attributes.Remove(require); 127 | } 128 | 129 | // ToArray is important as the collection can be modified 130 | foreach (var child in xml.ChildNodes.OfType().ToArray()) 131 | { 132 | ProcessElement(child, roles); 133 | } 134 | } 135 | 136 | internal static string ParseRole(XmlDocument webConfig) 137 | { 138 | return ((XmlElement)webConfig.DocumentElement.SelectSingleNode("appSettings/add[@key='role:define']"))?.GetAttribute("value"); 139 | } 140 | 141 | internal static void ChangeConfigProvider(XmlDocument webConfig, string filePath) 142 | { 143 | var section = ((XmlElement)webConfig.DocumentElement.SelectSingleNode("configSections/section[@name='sitecore']")); 144 | if (!section.GetAttribute("type").StartsWith("Sitecore.Configuration.Roles.RoleConfigReader")) 145 | { 146 | return; 147 | } 148 | 149 | section.SetAttribute("type", "Sitecore.Configuration.ConfigReader, Sitecore.Kernel"); 150 | webConfig.Save(filePath); 151 | } 152 | 153 | private static XmlDocument ReadWebConfigFile(string filePath) 154 | { 155 | var webConfig = new XmlDocument(); 156 | webConfig.Load(filePath); 157 | 158 | return webConfig; 159 | } 160 | 161 | private static string GetWebConfigPath(string[] args) 162 | { 163 | return GetFirstArgument(args); 164 | } 165 | 166 | private static string GetFirstArgument(string[] args) 167 | { 168 | return args[0]; 169 | } 170 | 171 | private static string GetOutputDirectoryPath(string[] args) 172 | { 173 | return args.Skip(1).FirstOrDefault(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles.UnitTests/RoleXmlPatchHelperTests.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles.UnitTests 2 | { 3 | using System.Xml; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Sitecore.Data.Items; 6 | using Sitecore.Xml.Patch; 7 | 8 | [TestClass] 9 | public class RoleXmlPatchHelperTests 10 | { 11 | [TestMethod] 12 | public void MergeChildrenTest_Root() 13 | { 14 | var sut = new RoleXmlPatchHelperEx(); 15 | var target = ParseXml(""); 16 | var patch = ParseIXml(""); 17 | 18 | sut.MergeChildren(target, patch, false); 19 | 20 | Assert.AreEqual(0, target.ChildNodes.Count); 21 | } 22 | 23 | [TestMethod] 24 | public void MergeChildrenTest_AddChild() 25 | { 26 | var sut = new RoleXmlPatchHelperEx(); 27 | var target = ParseXml(""); 28 | var patch = ParseIXml(""); 29 | 30 | sut.MergeChildren(target, patch, false); 31 | 32 | Assert.AreEqual(1, target.ChildNodes.Count); 33 | Assert.AreEqual("child", target.ChildNodes[0].Name); 34 | } 35 | 36 | [TestMethod] 37 | public void MergeChildrenTest_AddChildren() 38 | { 39 | var sut = new RoleXmlPatchHelperEx(); 40 | var target = ParseXml(""); 41 | var patch = ParseIXml(""); 42 | 43 | sut.MergeChildren(target, patch, false); 44 | 45 | Assert.AreEqual(2, target.ChildNodes.Count); 46 | Assert.AreEqual("child1", target.ChildNodes[0].Name); 47 | Assert.AreEqual("child2", target.ChildNodes[1].Name); 48 | } 49 | 50 | [TestMethod] 51 | public void MergeChildrenTest_RemoveChild() 52 | { 53 | var sut = new RoleXmlPatchHelperEx(); 54 | var target = ParseXml( 55 | "" + 56 | " " + 57 | " " + 58 | " " + 59 | ""); 60 | var patch = ParseIXml( 61 | "" + 62 | " " + 63 | " " + 64 | " " + 65 | " " + 66 | " " + 67 | ""); 68 | 69 | sut.MergeChildren(target, patch, false); 70 | 71 | Assert.AreEqual(0, target.ChildNodes[0].ChildNodes.Count); 72 | } 73 | 74 | [TestMethod] 75 | public void MergeChildrenTest_RemoveChild_Role1() 76 | { 77 | var sut = new RoleXmlPatchHelperEx("role1"); 78 | var target = ParseXml( 79 | "" + 80 | " " + 81 | " " + 82 | " " + 83 | " " + 84 | " " + 85 | ""); 86 | 87 | var patch = ParseIXml( 88 | "" + 89 | " " + 90 | " " + 91 | " " + 92 | " " + 93 | " " + 94 | " " + 95 | " " + 96 | " " + 97 | " " + 98 | " " + 99 | " " + 100 | ""); 101 | 102 | sut.MergeChildren(target, patch, false); 103 | 104 | Assert.AreEqual(2, target.ChildNodes[0].ChildNodes.Count); 105 | Assert.AreEqual("child2", target.ChildNodes[0].ChildNodes[0].Name); 106 | Assert.AreEqual("child3", target.ChildNodes[0].ChildNodes[1].Name); 107 | } 108 | 109 | [TestMethod] 110 | public void MergeChildrenTest_RemoveChild_Role2() 111 | { 112 | var sut = new RoleXmlPatchHelperEx("role2"); 113 | var target = ParseXml( 114 | "" + 115 | " " + 116 | " " + 117 | " " + 118 | " " + 119 | " " + 120 | ""); 121 | 122 | var patch = ParseIXml( 123 | "" + 124 | " " + 125 | " " + 126 | " " + 127 | " " + 128 | " " + 129 | " " + 130 | " " + 131 | " " + 132 | " " + 133 | " " + 134 | " " + 135 | ""); 136 | 137 | sut.MergeChildren(target, patch, false); 138 | 139 | Assert.AreEqual(2, target.ChildNodes[0].ChildNodes.Count); 140 | Assert.AreEqual("child1", target.ChildNodes[0].ChildNodes[0].Name); 141 | Assert.AreEqual("child3", target.ChildNodes[0].ChildNodes[1].Name); 142 | } 143 | 144 | [TestMethod] 145 | public void MergeChildrenTest_RemoveChild_Role3() 146 | { 147 | var sut = new RoleXmlPatchHelperEx("role3"); 148 | var target = ParseXml( 149 | "" + 150 | " " + 151 | " " + 152 | " " + 153 | " " + 154 | " " + 155 | ""); 156 | 157 | var patch = ParseIXml( 158 | "" + 159 | " " + 160 | " " + 161 | " " + 162 | " " + 163 | " " + 164 | " " + 165 | " " + 166 | " " + 167 | " " + 168 | " " + 169 | " " + 170 | ""); 171 | 172 | sut.MergeChildren(target, patch, false); 173 | 174 | Assert.AreEqual(2, target.ChildNodes[0].ChildNodes.Count); 175 | Assert.AreEqual("child1", target.ChildNodes[0].ChildNodes[0].Name); 176 | Assert.AreEqual("child2", target.ChildNodes[0].ChildNodes[1].Name); 177 | } 178 | 179 | private IXmlElement ParseIXml(string xml) 180 | { 181 | return new XmlDomSource(ParseXml(xml)); 182 | } 183 | 184 | private XmlElement ParseXml(string xml) 185 | { 186 | var doc = new XmlDocument(); 187 | doc.LoadXml(xml); 188 | 189 | return doc.DocumentElement; 190 | } 191 | 192 | private class RoleXmlPatchHelperEx : RoleXmlPatchHelper 193 | { 194 | private XmlPatchNamespaces Namespace { get; } 195 | = new XmlPatchNamespaces 196 | { 197 | PatchNamespace = "http://www.sitecore.net/xmlconfig/", 198 | SetNamespace = "http://www.sitecore.net/xmlconfig/set/" 199 | }; 200 | 201 | internal RoleXmlPatchHelperEx(params string[] roles) : base(new RoleConfigurationHelper(roles)) 202 | { 203 | } 204 | 205 | internal new void MergeChildren(XmlNode target, IXmlElement patch, bool targetWasInserted) 206 | { 207 | base.MergeChildren(target, patch, Namespace, targetWasInserted); 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sitecore Configuration Roles 2 | 3 | This document describes how to configure a Sitecore instance to use one of the pre-defined server roles. After you install a Sitecore instance, the only changes you need to make are to install the module and update settings that define which server role it will have. 4 | 5 | ## Available in Sitecore 9.0 out-of-box 6 | 7 | This project has become deprecated since **Sitecore 9.0.0** release, which offers same functionality with extra benefits such as: 8 | 9 | * refactored configuration files (stock `App_Config/Include/**` contents was moved to `App_Config/Sitecore`) 10 | * pre-configured roles (`App_Config/Sitecore.config` is annotated as well as `/App_Config/Sitecore/**`) 11 | * search engine support (`Lucene`, `Solr`, `Azure`) 12 | * custom prefixes (you can add as many `something:define="option1"` and `something:require="option2"` as you want) 13 | * layers of configuration via `App_Config/Layers.config` (added `App_Config/Environment` and `App_Config/Modules`) 14 | 15 | ### Index 16 | 17 | ##### [Prerequsites](#prerequsites) 18 | 19 | ##### [How To](#how-to) 20 | 1. [Install NuGet Package](#1-install-nuget-package) 21 | 2. [Replace Include Configuration Files](#2-replace-include-configuration-files) 22 | 3. [Deploy](#3-deploy) 23 | 4. [Update web.config files](#4-update-webconfig-files) 24 | 5. [Verify if it works](#5-verify-if-it-works) 25 | 26 | ##### [Details](#details) 27 | 1. [Define Role Command](#1--define-role-command) 28 | 2. [Require Role Command](#2--require-role-command) 29 | 3. [Modified configuration files](#3--modified-configuration-files) 30 | 31 | ##### [Comments](#comments) 32 | 33 | ## Prerequsites 34 | 35 | * **Sitecore CMS 8.1 rev. 160302 (Update-2)** 36 | * **Sitecore CMS 8.1 rev. 160302 (Update-3)** 37 | * **Sitecore CMS 8.2 - all releases** 38 | 39 | In this project Sitecore configuration engine was extended with two simple commands 40 | and modified configuration files that use them. It is distributed as a module which 41 | is based on the `Sitecore CMS 8.1 rev. 160302 (Update-2)` which now allows patching 42 | configuration engine. 43 | 44 | ## How to 45 | 46 | ### 1. Install NuGet Package 47 | 48 | Install the `Sitecore.Configuration.Roles` NuGet package: 49 | ```ps 50 | PS> Install-Package Sitecore.Configuration.Roles 51 | ``` 52 | Alternatively, you can [download it here](https://github.com/Sitecore/Sitecore-Configuration-Roles/releases) and unpack to the `bin` folder. 53 | 54 | ### 2. Replace Include Configuration Files 55 | 56 | Replace default Sitecore configuration files in `App_Config/Include` folder with annotated ones. 57 | 58 | Delete entire contents of `App_Config/Include` folder (**except `DataFolder.config` file and your custom files**) and replace with files from one of the branches: 59 | * Sitecore 8.1 Update-3 - [configuration/8.1.3](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.1.3) 60 | * Sitecore 8.2 Initial Release - [configuration/8.2.0](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.0) 61 | * Sitecore 8.2 Update-1 - [configuration/8.2.1](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.1) 62 | * Sitecore 8.2 Update-2 - [configuration/8.2.2](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.2) 63 | * Sitecore 8.2 Update-3 - [configuration/8.2.3](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.3) 64 | * Sitecore 8.2 Update-4 - [configuration/8.2.4](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.4) 65 | * Sitecore 8.2 Update-5 - [configuration/8.2.5](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.5) 66 | * Sitecore 8.2 Update-6 - [configuration/8.2.6](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.6) 67 | * Sitecore 8.2 Update-7 - [configuration/8.2.7](https://github.com/Sitecore/Sitecore-Configuration-Roles/tree/configuration/8.2.7) 68 | 69 | Go through your custom configuration files and annotate configuration nodes that must be presented only in certain kind of instances. 70 | 71 | For example, the item saved event handlers in `Customization.config` file to be used only in the `ContentManagement` environment: 72 | 73 | ```xml 74 | 75 | 76 | 77 | 78 | 79 | ``` 80 | 81 | ### 3. Deploy 82 | 83 | Deploy the files to both `ContentManagement` and `ContentDelivery` Sitecore instances: 84 | ``` 85 | App_Config/Include/**/* 86 | bin/Sitecore.Configuration.Roles.dll 87 | ``` 88 | 89 | ### 4. Update web.config files 90 | 91 | Change `web.config` files of `ContentManagement` and `ContentDelivery` Sitecore instances so they are aware of their role. 92 | 93 | #### ContentManagement 94 | 95 | ```xml 96 | ... 97 | 98 |
99 | ... 100 | 101 | 102 | ... 103 | 104 | 105 | ... 106 | ``` 107 | 108 | #### ContentDelivery 109 | 110 | ```xml 111 | ... 112 | 113 |
114 | ... 115 | 116 | 117 | ... 118 | 119 | 120 | ... 121 | ``` 122 | 123 | ### 5. Verify if it works 124 | 125 | (Optional) Verify actual configuration: 126 | * navigate to the `/sitecore/admin/showconfig.aspx` page of the `ContentDelivery` instance 127 | * make sure that the definition of the `sitecore_master_index` index configuration element is not presented on the page 128 | 129 | ## Details 130 | 131 | ### 1. Define Role Command 132 | 133 | The `role:define` command defines pipe-separated list of configuration roles the given Sitecore instance has. 134 | 135 | The role name can be any string that matches the `[a-zA-Z0-9]+` pattern, however 136 | there are several commonly used **conventional role names** to use: 137 | 138 | * Standalone 139 | * ContentManagement 140 | * Reporting 141 | * Processing 142 | * ContentDelivery 143 | 144 | These roles are described below. 145 | 146 | #### Example 147 | 148 | ```xml 149 | 150 | ... 151 | 152 | ... 153 | 154 | 155 | ... 156 | 157 | ``` 158 | 159 | ### 2. Require Role Command 160 | 161 | When `role:require` command is applied to a XML configuration node within Sitecore include config file 162 | the node will be ignored if the boolean expression is false. When the expression is evaluated, every 163 | configuration role that is defined by the `role:define` command is being transformed into "true" and 164 | all undefined role names are transformed into "false" condition. 165 | 166 | ### 3. Modified configuration files 167 | 168 | The module is shipped with modified stock configuration files to make Sitecore 169 | pre-configured to serve each of these configuration roles. 170 | 171 | #### Standalone 172 | 173 | Defines Standalone role that is the same as Sitecore pre-configured out of box. 174 | It allows only single-server set up. 175 | 176 | #### ContentManagement 177 | 178 | Defines Content Management (CM) role that allows editors to use editing 179 | applications like Content Editor, Page Editor etc. 180 | 181 | #### Reporting 182 | 183 | Defines xDB Reporting (Rep) role that fetches reporting data from various 184 | data sources to use in Sitecore reporting applications. It can be enabled 185 | on the same instance with other roles or on a dedicated Sitecore instance. 186 | 187 | #### Processing 188 | 189 | Defines xDB Processing (Proc) role. It can be enabled on the same instance 190 | with other roles or on a dedicated Sitecore instance. 191 | 192 | #### ContentDelivery 193 | 194 | Defines Content Delivery (CD) role that assumes current Sitecore instance 195 | is accessed only by end-users and Sitecore administrators. It cannot be 196 | enabled on the same instance with other roles. 197 | 198 | ## Examples 199 | 200 | EXAMPLE 1 201 | Here is an example of Sitecore solution with single Sitecore instance. 202 | 203 | - SRV-01: Standalone 204 | 205 | EXAMPLE 2 206 | Here is an example of Sitecore solution with 2 Sitecore instances: 207 | one is multipurpose, another is delivery only - both serve front-end users. 208 | 209 | - SRV-01: ContentManagement|Processing|Reporting 210 | - SRV-02: ContentDelivery 211 | 212 | EXAMPLE 3 213 | Here is an example of Sitecore solution with 5 Sitecore instances, 214 | one content-management only, one processing and reporting and 3 delivery. 215 | 216 | - SRV-01: ContentManagement 217 | - SRV-02: Processing|Reporting 218 | - SRV-03: ContentDelivery 219 | - SRV-04: ContentDelivery 220 | - SRV-05: ContentDelivery 221 | -------------------------------------------------------------------------------- /src/Sitecore.Configuration.Roles/RoleXmlPatchHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Sitecore.Configuration.Roles 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Xml; 7 | using Sitecore.Diagnostics; 8 | using Sitecore.Xml.Patch; 9 | 10 | public class RoleXmlPatchHelper : XmlPatchHelper 11 | { 12 | private const string RoleNamespace = "http://www.sitecore.net/xmlconfig/role/"; 13 | 14 | private readonly RoleConfigurationHelper RoleConfigurationHelper; 15 | 16 | public RoleXmlPatchHelper(RoleConfigurationHelper roleConfigurationHelper) 17 | { 18 | RoleConfigurationHelper = roleConfigurationHelper; 19 | } 20 | 21 | public override void CopyAttributes([NotNull] XmlNode target, [NotNull] IXmlElement patch, [NotNull] XmlPatchNamespaces ns) 22 | { 23 | Assert.ArgumentNotNull(target, "target"); 24 | Assert.ArgumentNotNull(patch, "patch"); 25 | Assert.ArgumentNotNull(ns, "ns"); 26 | 27 | var attributes = patch.GetAttributes().Where(a => a.NamespaceURI != ns.PatchNamespace && (a.NamespaceURI != RoleNamespace) && a.NamespaceURI != "http://www.w3.org/2000/xmlns/"); 28 | var values = attributes.Select(a => ParseXmlNodeInfo(ns, a)).ToArray(); 29 | 30 | if (!values.Any()) 31 | { 32 | return; 33 | } 34 | 35 | this.AssignAttributes(target, values); 36 | this.AssignSource(target, patch, ns); 37 | } 38 | 39 | public override void MergeNodes([NotNull] XmlNode target, [NotNull] IXmlElement patch, [NotNull] XmlPatchNamespaces ns) 40 | { 41 | Assert.ArgumentNotNull(target, "target"); 42 | Assert.ArgumentNotNull(patch, "patch"); 43 | Assert.ArgumentNotNull(ns, "ns"); 44 | 45 | if (target.NamespaceURI != patch.NamespaceURI || target.LocalName != patch.LocalName) 46 | { 47 | return; 48 | } 49 | 50 | var exit = false; 51 | foreach (var attribute in patch.GetAttributes()) 52 | { 53 | if (exit) 54 | { 55 | continue; 56 | } 57 | 58 | if (attribute.NamespaceURI == RoleNamespace && !RoleConfigurationHelper.ProcessRolesNamespace(attribute)) 59 | { 60 | // we need to finish enumerating attributes to avoid reader problem 61 | exit = true; 62 | } 63 | } 64 | 65 | if (exit) 66 | { 67 | foreach (var node in patch.GetChildren()) 68 | { 69 | // we need to get children to avoid reader problem 70 | } 71 | 72 | return; 73 | } 74 | 75 | base.MergeNodes(target, patch, ns); 76 | } 77 | 78 | protected override void MergeChildren(XmlNode target, IXmlElement patch, XmlPatchNamespaces ns, bool targetWasInserted) 79 | { 80 | Assert.ArgumentNotNull(target, "target"); 81 | Assert.ArgumentNotNull(patch, "patch"); 82 | Assert.ArgumentNotNull(ns, "ns"); 83 | 84 | string savedComment = null; 85 | var pendingOperations = new Stack(); 86 | 87 | // copy child nodes 88 | foreach (IXmlElement node in patch.GetChildren()) 89 | { 90 | if (node.NodeType == XmlNodeType.Text) 91 | { 92 | target.InnerText = node.Value; 93 | continue; 94 | } 95 | 96 | if (node.NodeType == XmlNodeType.Comment) 97 | { 98 | savedComment = node.Value; 99 | continue; 100 | } 101 | 102 | if (node.NodeType != XmlNodeType.Element) 103 | { 104 | continue; 105 | } 106 | 107 | if (node.NamespaceURI == ns.PatchNamespace) 108 | { 109 | ProcessConfigNode(target, node); 110 | 111 | continue; 112 | } 113 | 114 | var queryAttributes = new List(); 115 | var setAttributes = new List(); 116 | 117 | InsertOperation operation = null; 118 | 119 | var exit = false; 120 | foreach (IXmlNode attribute in node.GetAttributes()) 121 | { 122 | if (exit) 123 | { 124 | continue; 125 | } 126 | 127 | if (attribute.NamespaceURI == RoleNamespace) 128 | { 129 | if (!RoleConfigurationHelper.ProcessRolesNamespace(attribute)) 130 | { 131 | // we need to finish enumerating attributes to avoid reader problem 132 | exit = true; 133 | pendingOperations.Clear(); 134 | } 135 | 136 | continue; 137 | } 138 | 139 | if (attribute.NamespaceURI == ns.PatchNamespace) 140 | { 141 | switch (attribute.LocalName) 142 | { 143 | case "b": 144 | case "before": 145 | case "a": 146 | case "after": 147 | case "i": 148 | case "instead": 149 | operation = new InsertOperation 150 | { 151 | Reference = attribute.Value, 152 | Disposition = attribute.LocalName[0] 153 | }; 154 | break; 155 | } 156 | 157 | continue; 158 | } 159 | 160 | if (attribute.NamespaceURI == ns.SetNamespace) 161 | { 162 | setAttributes.Add(new XmlNodeInfo 163 | { 164 | NodeType = attribute.NodeType, 165 | NamespaceURI = string.Empty, // "set" NS translates into an empty NS 166 | LocalName = attribute.LocalName, 167 | Prefix = string.Empty, 168 | Value = attribute.Value 169 | }); 170 | continue; 171 | } 172 | 173 | if (attribute.Prefix != "xmlns") 174 | { 175 | queryAttributes.Add(new XmlNodeInfo 176 | { 177 | NodeType = attribute.NodeType, 178 | NamespaceURI = attribute.NamespaceURI, 179 | LocalName = attribute.LocalName, 180 | Prefix = attribute.Prefix, 181 | Value = attribute.Value 182 | }); 183 | } 184 | } 185 | 186 | if (exit) 187 | { 188 | continue; 189 | } 190 | 191 | var nsManager = new XmlNamespaceManager(new NameTable()); 192 | 193 | var predicateBuilder = new StringBuilder(); 194 | var added = false; 195 | foreach (var a in queryAttributes) 196 | { 197 | if (added) 198 | { 199 | predicateBuilder.Append(" and "); 200 | } 201 | 202 | if (a.Prefix != null && string.IsNullOrEmpty(nsManager.LookupPrefix(a.Prefix))) 203 | { 204 | nsManager.AddNamespace(a.Prefix, a.NamespaceURI); 205 | } 206 | 207 | predicateBuilder.Append("@" + MakeName(a.Prefix, a.LocalName) + "=\"" + a.Value + "\""); 208 | added = true; 209 | } 210 | 211 | if (node.Prefix != null && string.IsNullOrEmpty(nsManager.LookupPrefix(node.Prefix))) 212 | { 213 | nsManager.AddNamespace(node.Prefix, node.NamespaceURI); 214 | } 215 | 216 | XmlNode targetChild = null; 217 | bool created = false; 218 | 219 | if (!targetWasInserted) 220 | { 221 | string predicate = MakeName(node.Prefix, node.LocalName); 222 | 223 | var expression = predicateBuilder.ToString(); 224 | if (expression.Length > 0) 225 | { 226 | predicate = predicate + "[" + expression + "]"; 227 | } 228 | 229 | targetChild = target.SelectSingleNode(predicate, nsManager); 230 | } 231 | 232 | if (targetChild == null) 233 | { 234 | Assert.IsNotNull(target.OwnerDocument, "document"); 235 | targetChild = target.OwnerDocument.CreateElement(MakeName(node.Prefix, node.LocalName), node.NamespaceURI); 236 | created = true; 237 | if (!InsertChild(target, targetChild, operation) && operation != null) 238 | { 239 | operation.Node = targetChild; 240 | pendingOperations.Push(operation); 241 | } 242 | 243 | AssignAttributes(targetChild, queryAttributes); 244 | } 245 | else if (operation != null) 246 | { 247 | if (!InsertChild(target, targetChild, operation)) 248 | { 249 | operation.Node = targetChild; 250 | pendingOperations.Push(operation); 251 | } 252 | } 253 | 254 | if (savedComment != null) 255 | { 256 | Assert.IsNotNull(targetChild.OwnerDocument, "document"); 257 | XmlComment comment = targetChild.OwnerDocument.CreateComment(savedComment); 258 | Assert.IsNotNull(targetChild.ParentNode, "parent"); 259 | targetChild.ParentNode.InsertBefore(comment, targetChild); 260 | savedComment = null; 261 | } 262 | 263 | AssignAttributes(targetChild, setAttributes); 264 | MergeChildren(targetChild, node, ns, /*targetWasInserted = */ created); 265 | if ((created || setAttributes.Any()) && !targetWasInserted) 266 | { 267 | AssignSource(targetChild, node, ns); 268 | } 269 | } 270 | 271 | while (pendingOperations.Count > 0) 272 | { 273 | InsertOperation operation = pendingOperations.Pop(); 274 | Assert.IsNotNull(operation.Node.ParentNode, "parent"); 275 | InsertChild(operation.Node.ParentNode, operation.Node, operation); 276 | } 277 | } 278 | 279 | [NotNull] 280 | private static IXmlNode ParseXmlNodeInfo([NotNull] XmlPatchNamespaces ns, [NotNull] IXmlNode a) 281 | { 282 | Assert.ArgumentNotNull(ns, "ns"); 283 | Assert.ArgumentNotNull(a, "a"); 284 | 285 | var targetNamespace = a.NamespaceURI == ns.SetNamespace ? string.Empty : a.NamespaceURI; 286 | 287 | return new XmlNodeInfo 288 | { 289 | NodeType = a.NodeType, 290 | NamespaceURI = targetNamespace, 291 | LocalName = a.LocalName, 292 | Value = a.Value, 293 | Prefix = a.Prefix 294 | }; 295 | } 296 | } 297 | } --------------------------------------------------------------------------------