11package com .marklogic .appdeployer .command .security ;
22
3- import com .fasterxml .jackson .databind .JsonNode ;
4- import com .fasterxml .jackson .databind .node .ArrayNode ;
53import com .marklogic .appdeployer .command .AbstractResourceCommand ;
64import com .marklogic .appdeployer .command .CommandContext ;
75import com .marklogic .appdeployer .command .SortOrderConstants ;
8- import com .marklogic .mgmt .PayloadParser ;
9- import com .marklogic .mgmt .resource .ResourceManager ;
6+ import com .marklogic .mgmt .api .API ;
107import com .marklogic .mgmt .api .security .Role ;
8+ import com .marklogic .mgmt .mapper .DefaultResourceMapper ;
9+ import com .marklogic .mgmt .mapper .ResourceMapper ;
10+ import com .marklogic .mgmt .resource .ResourceManager ;
1111import com .marklogic .mgmt .resource .security .RoleManager ;
12- import com .marklogic .rest .util .Fragment ;
1312
1413import java .io .File ;
15- import java .util .*;
1614
1715public class DeployRolesCommand extends AbstractResourceCommand {
1816
19- private int maxSortAttempts = 100 ;
17+ // Used internally
18+ private boolean removeRolesAndPermissionsDuringDeployment = false ;
19+ private ResourceMapper resourceMapper ;
2020
2121 public DeployRolesCommand () {
2222 setExecuteSortOrder (SortOrderConstants .DEPLOY_ROLES );
2323 setUndoSortOrder (SortOrderConstants .DELETE_ROLES );
2424 }
2525
2626 /**
27- * Overriding this so we can list the files based on role dependencies.
27+ * The set of roles is processed twice. The first time, the roles are saved without any permissions or dependent roles.
28+ * This is to avoid issues where the roles depend on each other or on themselves. The second time, the roles are
29+ * saved with permissions and dependent roles, which is guaranteed to work now that the roles have all been created.
2830 *
29- * @param dir
30- * @return
31+ * @param context
3132 */
3233 @ Override
33- protected File [] listFilesInDirectory (File dir , CommandContext context ) {
34- File [] files = super .listFilesInDirectory (dir );
35-
36- if (context .getAppConfig ().isSortRolesByDependencies () && files != null && files .length > 0 ) {
37- if (logger .isInfoEnabled ()) {
38- logger .info ("Sorting role files by role dependencies" );
39- }
40- List <RoleFile > roleFiles = sortFilesBasedOnRoleDependencies (files , context );
41- files = new File [files .length ];
42- for (int i = 0 ; i < roleFiles .size (); i ++) {
43- files [i ] = roleFiles .get (i ).file ;
44- }
34+ public void execute (CommandContext context ) {
35+ removeRolesAndPermissionsDuringDeployment = true ;
36+ if (logger .isInfoEnabled ()) {
37+ logger .info ("Deploying roles without any permissions or dependent roles" );
4538 }
46-
47- return files ;
39+ super .execute (context );
40+ if (logger .isInfoEnabled ()) {
41+ logger .info ("Deploying roles with permissions and dependent roles" );
42+ }
43+ removeRolesAndPermissionsDuringDeployment = false ;
44+ super .execute (context );
4845 }
4946
50- protected List <RoleFile > sortFilesBasedOnRoleDependencies (File [] files , CommandContext context ) {
51- List <RoleFile > roleFiles = new ArrayList <>();
52- if (files == null || files .length < 1 ) {
53- return roleFiles ;
54- }
55- PayloadParser parser = new PayloadParser ();
56- for (File f : files ) {
57- RoleFile rf = new RoleFile (f );
58- String payload = copyFileToString (f , context );
59- if (parser .isJsonPayload (payload )) {
60- JsonNode json = parser .parseJson (payload );
61- rf .role .setRoleName (json .get ("role-name" ).asText ());
62- if (json .has ("role" )) {
63- ArrayNode roles = (ArrayNode ) json .get ("role" );
64- Iterator <JsonNode > iter = roles .elements ();
65- while (iter .hasNext ()) {
66- rf .role .getRole ().add (iter .next ().asText ());
67- }
68- }
69- } else {
70- Fragment frag = new Fragment (payload );
71- rf .role .setRoleName (frag .getElementValue ("/node()/m:role-name" ));
72- rf .role .setRole (frag .getElementValues ("/node()/m:roles/m:role" ));
47+ @ Override
48+ protected String adjustPayloadBeforeSavingResource (ResourceManager mgr , CommandContext context , File f , String payload ) {
49+ if (removeRolesAndPermissionsDuringDeployment ) {
50+ if (resourceMapper == null ) {
51+ API api = new API (context .getManageClient (), context .getAdminManager ());
52+ resourceMapper = new DefaultResourceMapper (api );
7353 }
74- roleFiles .add (rf );
54+ Role role = resourceMapper .readResource (payload , Role .class );
55+ role .clearPermissionsAndRoles ();
56+ return role .getJson ();
7557 }
76-
77- return keepSortingRoleFilesUntilOrderDoesntChange (roleFiles );
78- }
79-
80- /**
81- * Some sets of role files require multiple sorts until the order no longer changes. The maxSortAttempts class
82- * attribute controls how many times this command will try to sort the roles.
83- *
84- * @param roleFiles
85- * @return
86- */
87- protected List <RoleFile > keepSortingRoleFilesUntilOrderDoesntChange (List <RoleFile > roleFiles ) {
88- List <RoleFile > previousRoleFiles ;
89- int counter = 0 ;
90- do {
91- previousRoleFiles = roleFiles ;
92- roleFiles = new ArrayList <>();
93- roleFiles .addAll (previousRoleFiles );
94- roleFiles = sortRoleFiles (roleFiles );
95- counter ++;
96- } while (!previousRoleFiles .equals (roleFiles ) && counter < maxSortAttempts );
97- return roleFiles ;
98- }
99-
100- protected List <RoleFile > sortRoleFiles (List <RoleFile > roleFiles ) {
101- RoleFileComparator comparator = new RoleFileComparator (roleFiles );
102- Collections .sort (roleFiles , comparator );
103- return roleFiles ;
58+ return payload ;
10459 }
10560
10661 protected File [] getResourceDirs (CommandContext context ) {
@@ -111,104 +66,5 @@ protected File[] getResourceDirs(CommandContext context) {
11166 protected ResourceManager getResourceManager (CommandContext context ) {
11267 return new RoleManager (context .getManageClient ());
11368 }
114-
115- public void setMaxSortAttempts (int maxSortAttempts ) {
116- this .maxSortAttempts = maxSortAttempts ;
117- }
118- }
119-
120- /**
121- * Simple data structure for associating a File that defines a role, and the parsed Role that is used for
122- * sorting the role files.
123- */
124- class RoleFile {
125-
126- File file ;
127- Role role ;
128-
129- public RoleFile (File file ) {
130- this .file = file ;
131- this .role = new Role ();
132- this .role .setRole (new ArrayList <String >());
133- }
134-
135- @ Override
136- public int hashCode () {
137- return role .getRoleName ().hashCode ();
138- }
139-
140- @ Override
141- public boolean equals (Object obj ) {
142- return role .getRoleName ().equals (((RoleFile )obj ).role .getRoleName ());
143- }
14469}
14570
146- /**
147- * This comparator is designed to handle a scenario where two roles are next to each other, but they don't have any
148- * dependencies in common, nor does one depend on the other. In this scenario, we need to know which role has a
149- * dependency on a role furthest to the end of the list of roles. In order to know that, we first build up a data
150- * structure that tracks the highest position of a dependency in the list of roles for each role.
151- */
152- class RoleFileComparator implements Comparator <RoleFile > {
153-
154- private Map <String , Integer > highestDependencyPositionMap ;
155-
156- public RoleFileComparator (List <RoleFile > roleFiles ) {
157- Map <String , Integer > rolePositions = new HashMap <>();
158- for (int i = 0 ; i < roleFiles .size (); i ++) {
159- rolePositions .put (roleFiles .get (i ).role .getRoleName (), i );
160- }
161-
162- highestDependencyPositionMap = new HashMap <>();
163- for (RoleFile rf : roleFiles ) {
164- String roleName = rf .role .getRoleName ();
165- int highest = -1 ;
166- for (String role : rf .role .getRole ()) {
167- if (rolePositions .containsKey (role )) {
168- int pos = rolePositions .get (role );
169- if (pos > highest ) {
170- highest = pos ;
171- }
172- }
173- }
174- highestDependencyPositionMap .put (roleName , highest );
175- }
176- }
177-
178- @ Override
179- public int compare (RoleFile o1 , RoleFile o2 ) {
180- if (o1 .role .getRole ().isEmpty () && o2 .role .getRole ().isEmpty ()) {
181- return 0 ;
182- }
183- if (o1 .role .getRole ().isEmpty ()) {
184- return -1 ;
185- }
186- if (o2 .role .getRole ().isEmpty ()) {
187- return 1 ;
188- }
189- if (o2 .role .getRole ().contains (o1 .role .getRoleName ())) {
190- return -1 ;
191- }
192- if (o1 .role .getRole ().contains (o2 .role .getRoleName ())) {
193- return 1 ;
194- }
195-
196- /**
197- * If the roles aren't dependent on each other, then we want to base this on which role has a dependency further
198- * to the right.
199- */
200- int o1Pos = highestDependencyPositionMap .get (o1 .role .getRoleName ());
201- int o2Pos = highestDependencyPositionMap .get (o2 .role .getRoleName ());
202- if (o1Pos > o2Pos ) {
203- return 1 ;
204- }
205- if (o2Pos > o1Pos ) {
206- return -1 ;
207- }
208-
209- /**
210- * This would be for two roles that depend on the same other role.
211- */
212- return 0 ;
213- }
214- }
0 commit comments