001/* 002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Martin Pernollet 018 */ 019 020package org.nuxeo.ecm.platform.groups.audit.service.acl; 021 022import java.util.Collection; 023import java.util.HashSet; 024import java.util.Set; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.apache.poi.hssf.util.HSSFColor; 029import org.apache.poi.ss.usermodel.CellStyle; 030import org.apache.poi.ss.usermodel.Font; 031import org.apache.poi.ss.usermodel.Sheet; 032import org.nuxeo.ecm.core.api.CoreSession; 033import org.nuxeo.ecm.core.api.DocumentModel; 034import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 035import org.nuxeo.ecm.platform.groups.audit.service.acl.ReportLayoutSettings.SpanMode; 036import org.nuxeo.ecm.platform.groups.audit.service.acl.data.DataProcessor; 037import org.nuxeo.ecm.platform.groups.audit.service.acl.data.DataProcessor.ProcessorStatus; 038import org.nuxeo.ecm.platform.groups.audit.service.acl.data.DataProcessorPaginated; 039import org.nuxeo.ecm.platform.groups.audit.service.acl.data.DocumentSummary; 040import org.nuxeo.ecm.platform.groups.audit.service.acl.data.IDataProcessor; 041import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.AclNameShortner; 042import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.ByteColor; 043import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.ExcelBuilder; 044import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.ExcelBuilderMultiSheet; 045import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.IExcelBuilder; 046import org.nuxeo.ecm.platform.groups.audit.service.acl.excel.ExcelBuilder.Type; 047import org.nuxeo.ecm.platform.groups.audit.service.acl.filter.AcceptsAllContent; 048import org.nuxeo.ecm.platform.groups.audit.service.acl.filter.IContentFilter; 049import org.nuxeo.ecm.platform.groups.audit.service.acl.utils.MessageAccessor; 050 051import com.google.common.collect.Multimap; 052 053/** 054 * A builder works in three phases: 055 * <ul> 056 * <li>Fetch documents, possibly using paging. 057 * <li>Extract a document summary for each document. 058 * <li>Render documents' summary: 059 * <ul> 060 * <li>Render header and define column layout 061 * <li>Render file tree and define row layout 062 * <li>Render ACL matrix 063 * </ul> 064 * </ul> 065 * One can apply a {@link IContentFilter} to ignore some users/groups. This report builder uses one column per user, and 066 * write the list of existing ACL in one cell, by using "," as separator character. A denying ACL is indicated by !S, 067 * where S is the short name given to the ACL, as stated by the {@link AclNameShortner}. 068 * 069 * @author Martin Pernollet <mpernollet@nuxeo.com> 070 */ 071public class AclExcelLayoutBuilder implements IAclExcelLayoutBuilder { 072 protected static Log log = LogFactory.getLog(AclExcelLayoutBuilder.class); 073 074 protected static final String PROPERTY_MAIN_SHEET_NAME = "message.acl.audit.xl.mainsheet"; 075 076 protected static final String PROPERTY_LEGEND_SHEET_NAME = "message.acl.audit.xl.legend"; 077 078 protected static final String PROPERTY_LEGEND_LOCK_INHERITANCE = "message.acl.audit.xl.legend.lockInheritance"; 079 080 protected static final String PROPERTY_LEGEND_PERM_DENIED = "message.acl.audit.xl.legend.denied"; 081 082 protected IExcelBuilder excel = new ExcelBuilder(); 083 084 protected static int CELL_WIDTH_UNIT = 256; 085 086 public static int STATUS_ROW = 0; 087 088 public static int STATUS_COL = 0; 089 090 /* layout */ 091 protected ReportLayoutSettings layoutSettings; 092 093 protected ReportLayout layout; 094 095 protected int treeLineCursor = 0; 096 097 protected CellStyle userHeaderStyle; 098 099 protected CellStyle aclHeaderStyle; 100 101 protected CellStyle lockInheritanceStyle; 102 103 protected CellStyle grayTextStyle; 104 105 protected int mainSheetId; 106 107 protected int legendSheetId; 108 109 protected String mainSheetName; 110 111 protected String legendSheetName; 112 113 protected String legendLockInheritance = "Permission inheritance locked"; 114 115 protected String legendPermissionDenied = "Permission denied"; 116 117 public static ReportLayoutSettings defaultLayout() { 118 ReportLayoutSettings layout = new ReportLayoutSettings(); 119 layout.userHeaderHeight = 1000; 120 layout.userHeaderRotation = 45; 121 layout.fileTreeColumnWidth = 2; // in number of char 122 layout.aclColumnWidth = 4; 123 layout.defaultRowHeight = 100; 124 layout.splitPaneX = 500; 125 layout.splitPaneY = 1500; 126 layout.freezePaneRowSplit = 1; 127 layout.treeLineCursorRowStart = 1; 128 layout.spanMode = SpanMode.COLUMN_OVERFLOW_ON_NEXT_SHEETS; 129 layout.zoomRatioDenominator = 2; 130 layout.zoomRatioNumerator = 1; 131 layout.showFullPath = false; 132 133 // data fetch setting 134 layout.pageSize = 1000; 135 136 return layout; 137 } 138 139 /* tools */ 140 protected IContentFilter filter; 141 142 protected AclNameShortner shortner; 143 144 protected IDataProcessor data; 145 146 public AclExcelLayoutBuilder() { 147 this(defaultLayout()); 148 } 149 150 public AclExcelLayoutBuilder(IContentFilter filter) { 151 this(defaultLayout(), filter); 152 } 153 154 public AclExcelLayoutBuilder(ReportLayoutSettings layout) { 155 this(layout, null); 156 } 157 158 public AclExcelLayoutBuilder(ReportLayoutSettings layout, IContentFilter filter) { 159 this.layoutSettings = layout; 160 161 if (SpanMode.NONE.equals(layout.spanMode)) 162 excel = new ExcelBuilder(Type.XLS, "Permissions"); // missing context, no I18N 163 else if (SpanMode.COLUMN_OVERFLOW_ON_NEXT_SHEETS.equals(layout.spanMode)) { 164 excel = new ExcelBuilderMultiSheet(Type.XLS, "Permissions"); // missing context, no I18N 165 ((ExcelBuilderMultiSheet) excel).setMultiSheetColumns(true); 166 } else 167 throw new IllegalArgumentException("layout span mode unknown: " + layout.spanMode); 168 169 if (filter == null) 170 this.filter = new AcceptsAllContent(); 171 else 172 this.filter = filter; 173 174 if (layoutSettings.pageSize > 0) 175 this.data = new DataProcessorPaginated(this.filter, layoutSettings.pageSize); 176 else 177 this.data = new DataProcessor(this.filter); 178 179 this.shortner = new AclNameShortner(); 180 this.layout = new ReportLayout(); 181 } 182 183 @Override 184 public void renderAudit(CoreSession session) { 185 renderAudit(session, session.getRootDocument(), true); 186 } 187 188 @Override 189 public void renderAudit(CoreSession session, final DocumentModel doc) { 190 renderAudit(session, doc, true); 191 } 192 193 @Override 194 public void renderAudit(CoreSession session, final DocumentModel doc, boolean unrestricted) { 195 renderAudit(session, doc, unrestricted, 0); 196 } 197 198 @Override 199 public void renderAudit(CoreSession session, final DocumentModel doc, boolean unrestricted, final int timeout) 200 { 201 if (!unrestricted) { 202 analyzeAndRender(session, doc, timeout); 203 } else { 204 UnrestrictedSessionRunner runner = new UnrestrictedSessionRunner(session) { 205 @Override 206 public void run() { 207 analyzeAndRender(session, doc, timeout); 208 } 209 }; 210 runner.runUnrestricted(); 211 } 212 } 213 214 protected void analyzeAndRender(CoreSession session, final DocumentModel doc, int timeout) { 215 log.debug("start processing data"); 216 data.analyze(session, doc, timeout); 217 218 configure(session); 219 render(data); 220 } 221 222 /* EXCEL RENDERING */ 223 224 protected void configure(CoreSession session) { 225 // mainSheetName = MessageAccessor.get(session, PROPERTY_MAIN_SHEET_NAME); 226 legendSheetName = MessageAccessor.get(session, PROPERTY_LEGEND_SHEET_NAME); 227 legendLockInheritance = MessageAccessor.get(session, PROPERTY_LEGEND_LOCK_INHERITANCE); 228 legendPermissionDenied = MessageAccessor.get(session, PROPERTY_LEGEND_PERM_DENIED); 229 } 230 231 protected void render(IDataProcessor data) { 232 int minDepth = data.getDocumentTreeMinDepth(); 233 int maxDepth = data.getDocumentTreeMaxDepth(); 234 int colStart = maxDepth + (layoutSettings.showFullPath ? 1 : 0); 235 236 mainSheetId = excel.getCurrentSheetId(); 237 legendSheetId = excel.newSheet(excel.getCurrentSheetId() + 1, legendSheetName); 238 239 renderInit(); 240 renderHeader(colStart, data.getUserAndGroups(), data.getPermissions()); 241 renderFileTreeAndAclMatrix(data.getAllDocuments(), minDepth, maxDepth); 242 formatFileTreeCellLayout(maxDepth, minDepth, colStart); 243 renderLegend(data.getStatus(), data.getInformation()); 244 renderFinal(); 245 } 246 247 /** Initialize layout data model and pre-built cell styles */ 248 protected void renderInit() { 249 layout.reset(); 250 251 userHeaderStyle = excel.newCellStyle(); 252 userHeaderStyle.setFont(excel.getBoldFont()); 253 userHeaderStyle.setAlignment(CellStyle.ALIGN_CENTER); 254 if (layoutSettings.userHeaderRotation != 0) 255 userHeaderStyle.setRotation((short) layoutSettings.userHeaderRotation); 256 257 aclHeaderStyle = excel.newCellStyle(); 258 aclHeaderStyle.setFont(excel.newFont(layoutSettings.aclHeaderFontSize)); 259 aclHeaderStyle.setAlignment(CellStyle.ALIGN_CENTER); 260 if (layoutSettings.aclHeaderRotation != 0) 261 aclHeaderStyle.setRotation((short) layoutSettings.aclHeaderRotation); 262 263 lockInheritanceStyle = excel.newColoredCellStyle(ByteColor.BLUE); 264 265 grayTextStyle = excel.newCellStyle(); 266 Font f = excel.newFont(); 267 f.setColor(HSSFColor.GREY_50_PERCENT.index); 268 grayTextStyle.setFont(f); 269 // grayTextStyle.set 270 } 271 272 /** Perform various general tasks, such as setting the current sheet zoom. */ 273 protected void renderFinal() { 274 for (Sheet s : excel.getAllSheets()) { 275 s.setZoom(layoutSettings.zoomRatioNumerator, layoutSettings.zoomRatioDenominator); 276 } 277 } 278 279 /* HEADER RENDERING */ 280 281 /** 282 * Write users and groups on the first row. Memorize the user (or group) column which can later be retrieved with 283 * getColumn(user) 284 */ 285 protected void renderHeader(int tableStartColumn, Set<String> userOrGroups, Set<String> permission) { 286 renderHeaderUsers(tableStartColumn, userOrGroups); 287 } 288 289 protected void renderHeaderUsers(int tableStartColumn, Set<String> userOrGroups) { 290 int column = tableStartColumn; 291 for (String userOrGroup : userOrGroups) { 292 excel.setCell(0, column, userOrGroup, userHeaderStyle); 293 layout.setUserColumn(column, userOrGroup); 294 column++; 295 } 296 excel.setRowHeight(0, layoutSettings.userHeaderHeight); 297 } 298 299 /* FILE TREE AND MATRIX CONTENT RENDERING */ 300 301 protected void renderFileTreeAndAclMatrix(Collection<DocumentSummary> analyses, int minDepth, int maxDepth) 302 { 303 treeLineCursor = layoutSettings.treeLineCursorRowStart; 304 305 for (DocumentSummary summary : analyses) { 306 renderFilename(summary.getTitle(), summary.getDepth() - minDepth, summary.isAclLockInheritance()); 307 308 if (layoutSettings.showFullPath) 309 excel.setCell(treeLineCursor, maxDepth - minDepth + 1, summary.getPath()); 310 311 if (summary.getAclInheritedByUser() != null) 312 renderAcl(summary.getAclByUser(), summary.getAclInheritedByUser()); 313 else 314 renderAcl(summary.getAclByUser()); 315 treeLineCursor++; 316 } 317 } 318 319 protected void renderFilename(String title, int depth, boolean lockInheritance) { 320 // draw title 321 excel.setCell(treeLineCursor, depth, title); 322 323 // draw ace inheritance locker 324 if (depth > 0 && lockInheritance) { 325 excel.setCell(treeLineCursor, depth - 1, "", lockInheritanceStyle); 326 } 327 } 328 329 /** Render a row with all ACL of a given input file. */ 330 protected void renderAcl(Multimap<String, Pair<String, Boolean>> userAcls) { 331 renderAcl(userAcls, (CellStyle) null); 332 } 333 334 protected void renderAcl(Multimap<String, Pair<String, Boolean>> userAcls, CellStyle style) { 335 for (String user : userAcls.keySet()) { 336 int column = layout.getUserColumn(user); 337 String info = formatAcl(userAcls.get(user)); 338 excel.setCell(treeLineCursor, column, info, style); 339 } 340 } 341 342 /** 343 * Render local AND inherited ACL. 344 * <ul> 345 * <li>Local acl only are rendered with default font. 346 * <li>Inherited acl only are rendered with gray font. 347 * <li>Mixed acl (local and inherited) are rendered with default font. 348 * </ul> 349 */ 350 protected void renderAcl(Multimap<String, Pair<String, Boolean>> localAcls, 351 Multimap<String, Pair<String, Boolean>> inheritedAcls) { 352 Set<String> users = new HashSet<String>(); 353 users.addAll(localAcls.keySet()); 354 users.addAll(inheritedAcls.keySet()); 355 356 for (String user : users) { 357 int column = layout.getUserColumn(user); 358 String localAclsString = formatAcl(localAcls.get(user)); 359 String inheritedAclsString = formatAcl(inheritedAcls.get(user)); 360 361 if ("".equals(localAclsString) && "".equals(inheritedAclsString)) { 362 } else if (!"".equals(localAclsString) && !"".equals(inheritedAclsString)) { 363 String info = localAclsString + "," + inheritedAclsString; 364 excel.setCell(treeLineCursor, column, info); 365 } else if (!"".equals(localAclsString) && "".equals(inheritedAclsString)) { 366 String info = localAclsString; 367 excel.setCell(treeLineCursor, column, info); 368 } else if ("".equals(localAclsString) && !"".equals(inheritedAclsString)) { 369 String info = inheritedAclsString; 370 excel.setCell(treeLineCursor, column, info, grayTextStyle); 371 } 372 } 373 } 374 375 protected void renderLegend(ProcessorStatus status, String message) { 376 ((ExcelBuilderMultiSheet) excel).setMultiSheetColumns(false); 377 378 excel.setCurrentSheetId(legendSheetId); 379 380 int row = STATUS_ROW; 381 int col = STATUS_COL; 382 int off = renderLegendErrorMessage(row, col, status, message); 383 off = renderLegendAcl(off + 1, 0); 384 off++; 385 excel.setCell(off, col, "", lockInheritanceStyle); 386 excel.setCell(off, col + 1, legendLockInheritance); 387 off++; 388 } 389 390 protected int renderLegendErrorMessage(int row, int col, ProcessorStatus status, String message) { 391 if (!ProcessorStatus.SUCCESS.equals(status)) { 392 excel.setCell(row++, col, "Status: " + status); 393 if (message != null && !"".equals(message)) 394 excel.setCell(row++, col, "Message: " + message); 395 } 396 return row; 397 } 398 399 protected int renderLegendAcl(int row, int col) { 400 excel.setCell(row++, col, "ACL meaning"); 401 for (String shortName : shortner.getShortNames()) { 402 String fullName = shortner.getFullName(shortName); 403 excel.setCell(row, col, shortName); 404 excel.setCell(row, col + 1, fullName); 405 row++; 406 } 407 return row; 408 } 409 410 /* ACL TEXT FORMATTER FOR MATRIX */ 411 412 /** 413 * Renders all ACE separated by a , Each ACE name is formated using {@link formatAce(Pair<String, Boolean> ace)} 414 * 415 * @return 416 */ 417 protected String formatAcl(Collection<Pair<String, Boolean>> acls) { 418 StringBuilder sb = new StringBuilder(); 419 int k = 0; 420 for (Pair<String, Boolean> ace : acls) { 421 sb.append(formatAce(ace)); 422 if ((++k) < acls.size()) 423 sb.append(","); 424 } 425 return sb.toString(); 426 } 427 428 protected String formatAce(Pair<String, Boolean> ace) { 429 if (ace.b) 430 return formatPermission(ace.a); 431 else 432 return "!" + formatPermission(ace.a); 433 } 434 435 protected String formatPermission(String permission) { 436 return shortner.getShortName(permission); 437 } 438 439 /* CELL FORMATTER */ 440 441 /** 442 * Set column of size of each file tree column, and apply a freeze pan to fix the tree columns and header rows. 443 */ 444 protected void formatFileTreeCellLayout(int maxDepth, int minDepth, int colStart) { 445 int realMax = maxDepth - minDepth; 446 for (int i = 0; i < realMax; i++) { 447 excel.setColumnWidth(i, (int) (layoutSettings.fileTreeColumnWidth * CELL_WIDTH_UNIT)); 448 } 449 excel.setColumnWidthAuto(realMax); 450 excel.setFreezePane(colStart, layoutSettings.freezePaneRowSplit); 451 } 452 453 /* */ 454 455 /** {@inheritDoc} */ 456 @Override 457 public IExcelBuilder getExcel() { 458 return excel; 459 } 460}