001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.extensions.markup.html.repeater.data.table.export; 018 019import java.io.IOException; 020import java.io.OutputStream; 021import java.time.Duration; 022import java.util.LinkedList; 023import java.util.List; 024import org.apache.wicket.AttributeModifier; 025import org.apache.wicket.Component; 026import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractToolbar; 027import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; 028import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; 029import org.apache.wicket.markup.html.WebMarkupContainer; 030import org.apache.wicket.markup.html.basic.Label; 031import org.apache.wicket.markup.html.link.ResourceLink; 032import org.apache.wicket.markup.repeater.RepeatingView; 033import org.apache.wicket.markup.repeater.data.IDataProvider; 034import org.apache.wicket.model.IModel; 035import org.apache.wicket.model.ResourceModel; 036import org.apache.wicket.request.resource.IResource; 037import org.apache.wicket.request.resource.ResourceStreamResource; 038import org.apache.wicket.util.lang.Args; 039import org.apache.wicket.util.resource.AbstractResourceStreamWriter; 040import org.apache.wicket.util.resource.IResourceStream; 041import org.apache.wicket.util.resource.IResourceStreamWriter; 042 043/** 044 * A toolbar that provides links to download the data represented by all {@link IExportableColumn}s in the table 045 * exported to formats supported by the {@link IDataExporter}s configured. 046 * 047 * @author Jesse Long 048 * @see IDataExporter 049 * @see IExportableColumn 050 */ 051public class ExportToolbar extends AbstractToolbar 052{ 053 private static final long serialVersionUID = 1L; 054 055 private static final IModel<String> DEFAULT_MESSAGE_MODEL = new ResourceModel( 056 "datatable.export-to"); 057 058 private static final IModel<String> DEFAULT_FILE_NAME_MODEL = new ResourceModel( 059 "datatable.export-file-name"); 060 061 private final List<IDataExporter> dataExporters = new LinkedList<>(); 062 063 private IModel<String> messageModel; 064 065 private IModel<String> fileNameModel; 066 067 /** 068 * Creates a new instance with the default message model. This instance will use "export." as the exported 069 * file name prefix. 070 * 071 * @param table 072 * The data table this toolbar belongs to. 073 */ 074 public ExportToolbar(final DataTable<?, ?> table) 075 { 076 this(table, DEFAULT_MESSAGE_MODEL, DEFAULT_FILE_NAME_MODEL); 077 } 078 079 /** 080 * Creates a new instance with the provided data table and file name model. 081 * 082 * @param table 083 * The table to which this toolbar belongs. 084 * @param fileNameModel 085 * The model of the file name. This should exclude the file extensions. 086 */ 087 public ExportToolbar(DataTable<?, ?> table, IModel<String> fileNameModel) 088 { 089 this(table, DEFAULT_MESSAGE_MODEL, fileNameModel); 090 } 091 092 /** 093 * Creates a new instance. 094 * 095 * @param table 096 * The table to which this toolbar belongs. 097 * @param messageModel 098 * The model of the export message. 099 * @param fileNameModel 100 * The model of the file name. This should exclude the file extensions. 101 */ 102 public ExportToolbar(DataTable<?, ?> table, IModel<String> messageModel, IModel<String> fileNameModel) 103 { 104 super(table); 105 106 setMessageModel(messageModel); 107 setFileNameModel(fileNameModel); 108 } 109 110 /** 111 * Sets the models of the export message displayed in the toolbar. 112 * 113 * @param messageModel 114 * the models of the export message displayed in the toolbar. 115 * @return {@code this}, for chaining. 116 */ 117 public ExportToolbar setMessageModel(IModel<String> messageModel) 118 { 119 this.messageModel = wrap(Args.notNull(messageModel, "messageModel")); 120 return this; 121 } 122 123 /** 124 * Sets the model of the file name used for the exported data. 125 * 126 * @param fileNameModel 127 * The model of the file name used for the exported data. 128 * @return {@code this}, for chaining. 129 */ 130 public ExportToolbar setFileNameModel(IModel<String> fileNameModel) 131 { 132 this.fileNameModel = wrap(Args.notNull(fileNameModel, "fileNameModel")); 133 return this; 134 } 135 136 /** 137 * Returns the model of the file name used for the exported data. 138 * 139 * @return the model of the file name used for the exported data. 140 */ 141 public IModel<String> getFileNameModel() 142 { 143 return fileNameModel; 144 } 145 146 /** 147 * Returns the model of the export message displayed in the toolbar. 148 * 149 * @return the model of the export message displayed in the toolbar. 150 */ 151 public IModel<String> getMessageModel() 152 { 153 return messageModel; 154 } 155 156 /** 157 * {@inheritDoc } 158 */ 159 @Override 160 protected void onInitialize() 161 { 162 super.onInitialize(); 163 164 WebMarkupContainer td = new WebMarkupContainer("td"); 165 add(td); 166 167 td.add(AttributeModifier.replace("colspan", new IModel<String>() 168 { 169 private static final long serialVersionUID = 1L; 170 171 @Override 172 public String getObject() 173 { 174 return String.valueOf(getTable().getColumns().size()).intern(); 175 } 176 })); 177 178 td.add(new Label("exportTo", messageModel)); 179 180 RepeatingView linkContainers = new RepeatingView("linkContainer"); 181 td.add(linkContainers); 182 183 for (IDataExporter exporter : dataExporters) 184 { 185 WebMarkupContainer span = new WebMarkupContainer(linkContainers.newChildId()); 186 linkContainers.add(span); 187 188 span.add(createExportLink("exportLink", exporter)); 189 } 190 } 191 192 /** 193 * Creates a new link to the exported data for the provided {@link IDataExporter}. 194 * 195 * @param componentId 196 * The component of the link. 197 * @param dataExporter 198 * The data exporter to use to export the data. 199 * @return a new link to the exported data for the provided {@link IDataExporter}. 200 */ 201 protected Component createExportLink(String componentId, final IDataExporter dataExporter) 202 { 203 IResource resource = new ResourceStreamResource() 204 { 205 /** 206 * Set fileName and cacheDuration lazily 207 */ 208 public void respond(Attributes attributes) { 209 setFileName(fileNameModel.getObject() + "." + dataExporter.getFileNameExtension()); 210 setCacheDuration(ExportToolbar.this.getCacheDuration()); 211 212 super.respond(attributes); 213 } 214 215 @Override 216 protected IResourceStream getResourceStream(Attributes attributes) 217 { 218 return new DataExportResourceStreamWriter(dataExporter, getTable()); 219 } 220 }; 221 222 return new ResourceLink<Void>(componentId, resource) 223 .setBody(dataExporter.getDataFormatNameModel()); 224 } 225 226 /** 227 * How long should the export be cached. 228 * 229 * @return default is {@link Duration#ZERO} 230 */ 231 protected Duration getCacheDuration() { 232 return Duration.ZERO; 233 } 234 235 @Override 236 protected void onConfigure() 237 { 238 super.onConfigure(); 239 240 calculateVisibility(); 241 } 242 243 /** 244 * This toolbar is only visible if there are rows in the data set and if there are exportable columns in the 245 * data table and if there are data exporters added to the toolbar. 246 */ 247 protected void calculateVisibility() 248 { 249 final boolean isVisible; 250 if (dataExporters.isEmpty()) 251 { 252 isVisible = false; 253 } 254 else if (getTable().getRowCount() == 0) 255 { 256 isVisible = false; 257 } 258 else 259 { 260 boolean foundExportableColumn = false; 261 for (IColumn<?, ?> col : getTable().getColumns()) 262 { 263 if (col instanceof IExportableColumn) 264 { 265 foundExportableColumn = true; 266 break; 267 } 268 } 269 isVisible = foundExportableColumn; 270 } 271 272 setVisible(isVisible); 273 } 274 275 @Override 276 protected void onDetach() 277 { 278 fileNameModel.detach(); 279 messageModel.detach(); 280 super.onDetach(); 281 } 282 283 /** 284 * Adds a {@link IDataExporter} to the list of data exporters to be used in this toolbar. 285 * 286 * @param exporter 287 * The {@link IDataExporter} to add to the toolbar. 288 * @return {@code this}, for chaining. 289 */ 290 public ExportToolbar addDataExporter(IDataExporter exporter) 291 { 292 Args.notNull(exporter, "exporter"); 293 dataExporters.add(exporter); 294 return this; 295 } 296 297 /** 298 * An {@link IResourceStreamWriter} which writes the exportable data from a table to an output stream. 299 */ 300 public static class DataExportResourceStreamWriter extends AbstractResourceStreamWriter 301 { 302 private final IDataExporter dataExporter; 303 304 private final DataTable<?, ?> dataTable; 305 306 /** 307 * Creates a new instance using the provided {@link IDataExporter} to export data. 308 * 309 * @param dataExporter 310 * The {@link IDataExporter} to use to export data. 311 * @param dataTable 312 * The {@link DataTable} from which to export. 313 */ 314 public DataExportResourceStreamWriter(IDataExporter dataExporter, DataTable<?, ?> dataTable) 315 { 316 this.dataExporter = dataExporter; 317 this.dataTable = dataTable; 318 } 319 320 /** 321 * Writes the exported data to the output stream. This implementation calls 322 * {@link #exportData(org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable, org.apache.wicket.extensions.markup.html.repeater.data.table.export.IDataExporter, java.io.OutputStream) }. 323 * 324 * @param output 325 * The output stream to which to export the data. 326 * @throws IOException if an error occurs. 327 */ 328 @Override 329 public void write(OutputStream output) 330 throws IOException 331 { 332 exportData(dataTable, dataExporter, output); 333 } 334 335 /** 336 * {@inheritDoc} 337 * <p> 338 * This method returns the content type returned by {@link IDataExporter#getContentType()}. 339 * 340 * @return the content type returned by {@link IDataExporter#getContentType()}. 341 */ 342 @Override 343 public String getContentType() 344 { 345 return dataExporter.getContentType(); 346 } 347 348 /** 349 * Exports the data from the provided data table to the provided output stream. 350 * This methods calls {@link IDataExporter#exportData(org.apache.wicket.markup.repeater.data.IDataProvider, java.util.List, java.io.OutputStream) } 351 * passing it the {@link IDataProvider} of the {@link DataTable}, and a list of the {@link IExportableColumn}s in the table. 352 * 353 * @param <T> 354 * The type of each row in the data table. 355 * @param <S> 356 * The type of the sort property of the table. 357 * @param dataTable 358 * The {@link DataTable} to export. 359 * @param dataExporter 360 * The {@link IDataExporter} to use to export the data. 361 * @param outputStream 362 * The {@link OutputStream} to which the data should be exported to. 363 * @throws IOException 364 */ 365 private <T, S> void exportData(DataTable<T, S> dataTable, IDataExporter dataExporter, OutputStream outputStream) 366 throws IOException 367 { 368 IDataProvider<T> dataProvider = dataTable.getDataProvider(); 369 List<IExportableColumn<T, ?>> exportableColumns = new LinkedList<>(); 370 for (IColumn<T, S> col : dataTable.getColumns()) 371 { 372 if (col instanceof IExportableColumn) 373 { 374 exportableColumns.add((IExportableColumn<T, ?>)col); 375 } 376 } 377 dataExporter.exportData(dataProvider, exportableColumns, outputStream); 378 } 379 } 380}