Java tutorial
/******************************************************************************* * Copyright 2015 DANS - Data Archiving and Networked Services * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package nl.knaw.dans.dccd.web.search; import java.io.StringWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.wicket.Page; import org.apache.wicket.Resource; import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton; import org.apache.wicket.markup.html.DynamicWebResource; import org.apache.wicket.markup.html.WebResource; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Button; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.link.ResourceLink; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.protocol.http.WebResponse; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.resource.StringResourceStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nl.knaw.dans.common.lang.search.SearchHit; import nl.knaw.dans.common.lang.search.SearchRequest; import nl.knaw.dans.common.lang.search.SearchResult; import nl.knaw.dans.common.lang.search.bean.StringListCollapserConverter; import nl.knaw.dans.common.lang.search.exceptions.SearchBeanConverterException; import nl.knaw.dans.common.lang.search.simple.SimpleSearchRequest; import nl.knaw.dans.common.wicket.components.UnescapedLabel; import nl.knaw.dans.common.wicket.components.search.SearchPanel; import nl.knaw.dans.common.wicket.components.search.SearchResources; import nl.knaw.dans.common.wicket.components.search.model.SearchData; import nl.knaw.dans.common.wicket.components.search.model.SearchModel; import nl.knaw.dans.common.wicket.components.search.model.SearchRequestBuilder; import nl.knaw.dans.common.wicket.exceptions.InternalWebError; import nl.knaw.dans.dccd.model.DccdUser; import nl.knaw.dans.dccd.model.ProjectPermissionLevel; import nl.knaw.dans.dccd.search.DccdSB; import nl.knaw.dans.dccd.util.StringUtil; import nl.knaw.dans.dccd.web.DccdSession; import nl.knaw.dans.dccd.web.HomePage; import nl.knaw.dans.dccd.web.base.BasePage; import nl.knaw.dans.dccd.web.search.pages.LocationSearchResultPage; public class SearchResultDownloadPage extends BasePage { private static final Logger logger = LoggerFactory.getLogger(SearchResultDownloadPage.class); private static final String MIMETYPE_XML = "text/xml"; private static final String FILE_EXTENSION_XML = "xml"; private static final String MIMETYPE_CSV = "text/csv"; private static final String FILE_EXTENSION_CSV = "csv"; // the Unicode char represented by the UTF-8 byte order mark (EF BB BF). private static final String UTF8_BOM = "\uFEFF"; private static final String NAME_RESULTS = "results"; private DccdUser user; private SearchPanel callingPanel; public transient CharSequence theData = null; private Page backPage; public SearchResultDownloadPage(SearchModel model, final DccdSearchResultPanel callingPanel) { super(model); if (callingPanel == null) throw new IllegalArgumentException("calling panel should not be null"); this.callingPanel = callingPanel; init(); } private void init() { // get the back page backPage = ((DccdSession) Session.get()).getRedirectPage(getPage().getClass()); // The back button or link Link backButton = new Link("backButton", new ResourceModel("backButton")) { private static final long serialVersionUID = 1L; @Override public void onClick() { // get the previous page, and try to browse back Page page = backPage; if (page != null) { // if (page instanceof BasePage) // ((BasePage)page).refresh(); setResponsePage(page); } else { // just go back to a new instance of HomePage setResponsePage(HomePage.class); } } }; add(backButton); // result message // Not the a panel for displaying just the total results message //add(new SearchResultMessagePanel("resultMessage", (SearchModel)getDefaultModel())); SearchData searchData = ((SearchModel) getDefaultModel()).getObject(); SearchResult<? extends DccdSB> searchResult = (SearchResult<? extends DccdSB>) searchData.getResult(); SearchRequestBuilder originalRequestBuilder = searchData.getRequestBuilder(); int totalHits = searchResult.getTotalHits(); String queryString = originalRequestBuilder.getRequest().getQuery().getQueryString(); add(new UnescapedLabel("resultMessage", new StringResourceModel("search.download.resultMessage", this, null, new Object[] { totalHits, queryString }))); // Need the user for the permission check user = (DccdUser) ((DccdSession) getSession()).getUser(); // add form Form form = new Form("form"); add(form); // Note mabe use a DynamicWebResource? WebResource export = new WebResource() { private static final long serialVersionUID = -3015006109107869870L; @Override public IResourceStream getResourceStream() { return getResourceStreamForXML(); } @Override protected void setHeaders(WebResponse response) { super.setHeaders(response); setHeadersForXML(response); } }; export.setCacheable(false); final ResourceLink downloadLink = new ResourceLink("downloadSearchResults", export); downloadLink.setOutputMarkupPlaceholderTag(true); downloadLink.setVisible(false); downloadLink.setOutputMarkupId(true); form.add(downloadLink); final Label waitMsgLabel = new Label("myLabel", new StringResourceModel("search.download.fileconstruction.waitMessage", this, null)); waitMsgLabel.setOutputMarkupPlaceholderTag(true); waitMsgLabel.setVisible(true); waitMsgLabel.setOutputMarkupId(true); form.add(waitMsgLabel); IndicatingAjaxButton submitButton = new IndicatingAjaxButton("submitButton") { protected void onSubmit(AjaxRequestTarget target, Form<?> form) { // TODO Auto-generated method stub logger.debug("Onsubmit called, start work"); theData = getAllResultsAsXMLInParts();//getAllResultsAsXML(); logger.debug("Done with work"); // TODO enable downlod button? downloadLink.setVisible(true); target.addComponent(downloadLink); waitMsgLabel.setVisible(false); target.addComponent(waitMsgLabel); this.setVisible(false); target.addComponent(this); } }; submitButton.setOutputMarkupId(true); form.add(submitButton); } //--- XML --- private IResourceStream getResourceStreamForXML() { CharSequence txt = theData;//getAllResultsAsXML(); StringResourceStream rs = new StringResourceStream(txt, MIMETYPE_XML); rs.setCharset(Charset.forName("UTF-8")); return rs; } private void setHeadersForXML(WebResponse response) { // construct filename final String FILE_BASENAME = "dccdsearchresults"; String filename = FILE_BASENAME; // NOTE maybe add a timestamp, but browsers/OS will offer a numbering to the user? filename = filename + "." + FILE_EXTENSION_XML; response.setAttachmentHeader(filename); } // Get all hits for the search request, which is originally paged for easy of use in the GUI private SearchResult<? extends DccdSB> getAllResults() { if (user == null) { // Not allowed, throw exception logger.debug("Not logged in while constructing SearchResults for Download"); throw new InternalWebError(); } SearchData searchData = ((SearchModel) getDefaultModel()).getObject(); SearchResult<? extends DccdSB> searchResult = (SearchResult<? extends DccdSB>) searchData.getResult(); int limitForAll = searchResult.getTotalHits(); return getResultsFor(0, limitForAll); } private SearchResult<? extends DccdSB> getResultsFor(final int offset, final int limit) { SearchData searchData = ((SearchModel) getDefaultModel()).getObject(); SearchRequestBuilder originalRequestBuilder = searchData.getRequestBuilder(); SearchRequestBuilder requestBuilder = new SearchRequestBuilder(); requestBuilder.setLimit(limit); requestBuilder.setOffset(offset); // Use the same criteria // we are not changing the criteria so we can pass the list // Note that if criteria would change then we would need to implement a deep copy requestBuilder.setCriteria(originalRequestBuilder.getCriteria()); // leave sorting and facets alone and construct the request SimpleSearchRequest request = requestBuilder.getRequest(); // The request needs to be prepared for the search by adding filters dependent on the type of Page return (SearchResult<? extends DccdSB>) callingPanel.search(request); } // Produce the xml string // Note that it is done without an XML lib, but could use the DOM4J XMLWriter private String getAllResultsAsXMLInParts() { logger.debug("Start constructing result for download"); java.io.StringWriter sw = new StringWriter(); sw.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"); // XML instruction sw.append("<" + NAME_RESULTS + ">"); logger.debug("Constructing search results in XML for download"); // do it in parts, a bit like paging int partSize = 200; SearchData searchData = ((SearchModel) getDefaultModel()).getObject(); SearchResult<? extends DccdSB> searchResult = (SearchResult<? extends DccdSB>) searchData.getResult(); int totalNumberOfHits = searchResult.getTotalHits(); int numberOfParts = (totalNumberOfHits + partSize - 1) / partSize; // round up for (int partIndex = 0; partIndex < numberOfParts; partIndex++) { int offset = partIndex * partSize; logger.debug("Start part at: " + offset); SearchResult<? extends DccdSB> searchResults = getResultsFor(offset, partSize); sw.append(SearchResultDataConverter.getResultsAsXML(searchResults, user)); } sw.append("</" + NAME_RESULTS + ">"); logger.debug("End constructing result for download"); return sw.toString(); } //--- CSV, just for testing, but that has UTF-8 problems when importing into MS-Excel --- private IResourceStream getResourceStreamForCSV() { // prepend BOM, some programs like it others don't CharSequence txt = UTF8_BOM + getDataAsTabDelimitedText(); StringResourceStream rs = new StringResourceStream(txt, MIMETYPE_CSV); rs.setCharset(Charset.forName("UTF-8")); return rs; } private void setHeadersForCSV(WebResponse response) { // construct filename final String FILE_BASENAME = "dccdsearchresults"; String filename = FILE_BASENAME; // NOTE maybe add a timestamp, but browsers/OS will offer a numbering to the user? filename = filename + "." + FILE_EXTENSION_CSV; response.setAttachmentHeader(filename); } // NOTE just a test! private String getDataAsTabDelimitedText() { java.io.StringWriter sw = new StringWriter(); // TODO add a line for each search Hit for (int i = 0; i < 10; i++) { sw.append("Just \t some \t test\n"); } sw.append("Mehr Informationen \t ber die DCCD Nutzung \t finden Sie hier."); return sw.toString(); } }