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