Java tutorial
/** * * BibSonomy-Webapp - The webapplication for Bibsonomy. * * Copyright (C) 2006 - 2011 Knowledge & Data Engineering Group, * University of Kassel, Germany * http://www.kde.cs.uni-kassel.de/ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.bibsonomy.webapp.controller.actions; import static org.bibsonomy.util.ValidationUtils.present; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bibsonomy.bibtex.parser.PostBibTeXParser; import org.bibsonomy.common.enums.PostUpdateOperation; import org.bibsonomy.common.errors.DuplicatePostErrorMessage; import org.bibsonomy.common.errors.ErrorMessage; import org.bibsonomy.common.errors.SystemTagErrorMessage; import org.bibsonomy.common.exceptions.DatabaseException; import org.bibsonomy.common.exceptions.UnsupportedFileTypeException; import org.bibsonomy.model.BibTex; import org.bibsonomy.model.Document; import org.bibsonomy.model.Post; import org.bibsonomy.model.Tag; import org.bibsonomy.model.util.GroupUtils; import org.bibsonomy.model.util.TagUtils; import org.bibsonomy.scraper.converter.EndnoteToBibtexConverter; import org.bibsonomy.scraper.converter.RisToBibtexConverter; import org.bibsonomy.scraper.exceptions.ConversionException; import org.bibsonomy.util.StringUtils; import org.bibsonomy.util.upload.FileUploadInterface; import org.bibsonomy.util.upload.impl.FileUploadFactory; import org.bibsonomy.webapp.command.ListCommand; import org.bibsonomy.webapp.command.actions.PostPublicationCommand; import org.bibsonomy.webapp.util.GroupingCommandUtils; import org.bibsonomy.webapp.util.RequestWrapperContext; import org.bibsonomy.webapp.util.View; import org.bibsonomy.webapp.util.spring.security.exceptions.AccessDeniedNoticeException; import org.bibsonomy.webapp.validation.PostPublicationCommandValidator; import org.bibsonomy.webapp.view.Views; import org.springframework.validation.ValidationUtils; import org.springframework.web.multipart.commons.CommonsMultipartFile; import bibtex.parser.ParseException; /** * * @author ema * @version $Id: PostPublicationController.java,v 1.80 2011-07-14 14:06:30 nosebrain Exp $ */ public class PostPublicationController extends AbstractEditPublicationController<PostPublicationCommand> { /** * the log... */ private static final Log log = LogFactory.getLog(PostPublicationController.class); /** * if the user tries to import more than MAXCOUNT_ERRORHANDLING posts AND an error exists * in one or more of the posts, the correct posts will be saved no matter what. */ private static final Integer MAXCOUNT_ERRORHANDLING = 1000; /** * The session dictionary name for temporarily stored publications. * Will be used when PostPublicationCommand.editBeforeImport is true. */ public static final String TEMPORARILY_IMPORTED_PUBLICATIONS = "TEMPORARILY_IMPORTED_PUBLICATIONS"; /** * Extracts the line number from the parser error messages. */ private static final Pattern lineNumberPattern = Pattern.compile("([0-9]+)"); /** * the factory used to get an instance of a FileUploadHandler. */ private FileUploadFactory uploadFactory; /** * TODO: we could inject this object using Spring. */ private final EndnoteToBibtexConverter e2bConverter = new EndnoteToBibtexConverter(); @Override public PostPublicationCommand instantiateCommand() { /** * initialize post & resource */ final PostPublicationCommand command = new PostPublicationCommand(); command.setGroups(new ArrayList<String>()); command.setPost(new Post<BibTex>()); command.setAbstractGrouping(GroupUtils.getPublicGroup().getName()); command.getPost().setResource(new BibTex()); return command; } @Override public View workOn(final PostPublicationCommand command) { log.debug("workOn started"); final RequestWrapperContext context = command.getContext(); /* * only users which are logged in might post -> send them to * login page */ final BibTex publication = command.getPost().getResource(); if (!context.isUserLoggedIn()) { throw new AccessDeniedNoticeException("please log in", LOGIN_NOTICE + publication.getClass().getSimpleName().toLowerCase()); } /* * If the user entered the post data manually, the EditPublicationController * will handle the remaining work. * * To find out, if the data was entered manually, a good heuristic is to * check if an entrytype is given, because that field can't be empty. */ if (present(publication.getEntrytype())) { log.debug("user has manually entered post data -> forwarding to edit post controller"); return super.workOn(command); } /* * This variable will hold the information contained in the bibtex/endnote-file or selection field */ String snippet = null; /* * This handles the cases * 1) the user just started the postPublication process * 2) the user entered a snippet (might be empty) * 3) the user selected a file to upload posts (might be empty) * DOI/ISBN or manual input are handled in EditPostController */ final String selection = command.getSelection(); if (present(selection)) { /* * The user has entered text into the snippet selection - we use that */ log.debug("user has filled selection"); snippet = this.handleSelection(command, selection); } else if (present(command.getFile())) { /* * The user uploads a BibTeX or EndNote file */ log.debug("user uploads a file"); // get the (never empty) content or add corresponding errors snippet = handleFileUpload(command); } else { /* * nothing given -> * user just opened the postPublication Dialogue OR * user send empty snippet or "nonexisting" file * FIXME: that second case should result in some error and hint for the user */ return Views.POST_PUBLICATION; } /* * Either a file or a snippet was given, * it's content is now stored in snippet * -> check if valid */ if (errors.hasErrors()) { log.debug("errors found, returning to view"); if (log.isDebugEnabled()) { log.debug(errors); } return Views.POST_PUBLICATION; } /* * Extract posts from snippet ... */ /* * configure the parser */ final PostBibTeXParser parser = new PostBibTeXParser(); parser.setDelimiter(command.getDelimiter()); parser.setWhitespace(command.getWhitespace()); parser.setTryParseAll(true); /* * FIXME: why aren't commas, etc. removed? */ List<Post<BibTex>> posts = null; try { /* * Parse the BibTeX snippet */ posts = parser.parseBibTeXPosts(snippet); } catch (final ParseException ex) { errors.reject("error.upload.failed.parse", ex.getMessage()); } catch (final IOException ex) { errors.reject("error.upload.failed.parse", ex.getMessage()); } /* * The errors we have collected until now should be fixed before we proceed. * * (We did not collect errors due to individual broken BibTeX lines, yet!) */ if (errors.hasErrors()) { return Views.POST_PUBLICATION; } /* * turn parse exceptions into error messages ... */ handleParseExceptions(parser.getCaughtExceptions()); if (!errors.hasErrors() && !present(posts)) { /* * no errors ... but also no posts ... Ooops! * the parser was not able to produce posts but did not add errors nor throw exceptions */ errors.reject("error.upload.failed.parse", "Upload failed because of parser errors."); return Views.POST_PUBLICATION; } /* * If exactly one post has been extracted, and there were no parse exceptions, * the edit post controller can handle the remaining work. */ if (posts.size() == 1 && !errors.hasErrors()) { final Post<BibTex> post = posts.get(0); if (present(post)) { /* * Delete the selection, otherwise the AbstractEditPublicationControllers * workOnCommand() method would try to scrape it. */ command.setSelection(null); command.setPost(post); /* * When exactly one post is imported, its tags are not put into * the tag field. Instead, we show them here as "tags of copied post". */ command.setCopytags(new LinkedList<Tag>(post.getTags())); return super.workOn(command); } } /* * Complete the posts with missing information: * * add additional information from the form to the * post (description, groups)... present in both upload tabs */ for (final Post<BibTex> post : posts) { post.setUser(context.getLoginUser()); post.setDescription(command.getDescription()); if (!present(post.getTags())) { post.setTags(Collections.singleton(TagUtils.getImportedTag())); } /* * set visibility of this post for the groups, the user specified */ GroupingCommandUtils.initGroups(command, post.getGroups()); /* * hashes have to be set, in order to call the validator */ post.getResource().recalculateHashes(); } /* * add list of posts to command for showing them to the user * (such that he can edit them) */ final ListCommand<Post<BibTex>> postListCommand = new ListCommand<Post<BibTex>>(command); postListCommand.setList(posts); /* * FIXME: rename the "bibtex" attribute of the command (hint: we try * to avoid the name "bibtex" wherever possible) * (hint: errors.pushNestedPath("bibtex"); in the PostPublicationCommandValidator * then has to be adapted, too. As does the code in the JSPs, of course.) */ command.setBibtex(postListCommand); /* * validate the posts */ ValidationUtils.invokeValidator(new PostPublicationCommandValidator(), command, errors); /* * We try to store only posts that have no validation errors. */ final Map<Post<BibTex>, Integer> postsToStore = getPostsWithNoValidationErrors(posts); log.debug("will try to store " + postsToStore.size() + " of " + posts.size() + " posts in database"); /* * finally store the posts */ if (command.isEditBeforeImport()) { /* * user wants to edit the posts before storing them * -> put them into the session */ setSessionAttribute(TEMPORARILY_IMPORTED_PUBLICATIONS, posts); } else { /* * the publications are saved in the database */ storePosts(postsToStore, command.getOverwrite()); } /* * If there were any errors, some posts were not stored in the database. We * need to get them from the session later on, thus we store them there. */ if (errors.hasErrors()) { /* * Trigger the correct setting of the "delete/save" check boxes on * the batch edit page. */ command.setDeleteCheckedPosts(false); /* * save posts in session */ setSessionAttribute(TEMPORARILY_IMPORTED_PUBLICATIONS, posts); } /* * If the user wants to store the posts permanently AND (his posts have * no errors OR he ignores the errors OR the number of bibtexes is * greater than the treshold, we will forward him to the appropriate * site, where he can delete posts (they were saved) */ if (!command.isEditBeforeImport() && (!errors.hasErrors() || posts.size() > MAXCOUNT_ERRORHANDLING)) { command.setDeleteCheckedPosts(true); //posts will have to get saved, because the user decided to } else { command.setDeleteCheckedPosts(false); } /* * If there are errors now or not - we return to the post * publication view to let the user edit his posts. */ return Views.POST_PUBLICATION; } /** * Convertes a String into a BibTeX String * if selection is BibTeX nothing happens * if selection is EndNote is will be converted to BibTex * @param selection * @return */ private String handleSelection(final PostPublicationCommand command, final String selection) { // FIXME: at this point we must first convert to bibtex! if (EndnoteToBibtexConverter.canHandle(selection)) { return this.e2bConverter.endnoteToBibtex(selection); } if (RisToBibtexConverter.canHandle(selection)) { return new RisToBibtexConverter().RisToBibtex(selection); } /* * should be BibTeX */ return selection; } /** * Checks each post for validation errors and returns only those posts, * that don't have any errors. The posts are returned in a hashmap, where * each post points to its position in the original list such that we can * later add errors (from the database) at the correct position. * * @param posts * @return */ private Map<Post<BibTex>, Integer> getPostsWithNoValidationErrors(final List<Post<BibTex>> posts) { final Map<Post<BibTex>, Integer> storageList = new LinkedHashMap<Post<BibTex>, Integer>(); /* * iterate over all posts */ for (int i = 0; i < posts.size(); i++) { /* * check, if this post has field errors */ if (!present(errors.getFieldErrors("bibtex.list[" + i + "]*"))) { log.debug("post no. " + i + " has no field errors"); /* * post has no field errors --> try to store it in database * * We also remember the original position of the post to * add error messages later. */ storageList.put(posts.get(i), i); } } return storageList; } /** * Extracts the parse exceptions and adds the line numbers with errors * to the errors object. * * @param parseExceptions */ private void handleParseExceptions(final ParseException[] parseExceptions) { final StringBuilder buf = new StringBuilder(); boolean lineFound = false; for (final ParseException parseException : parseExceptions) { final Matcher m = lineNumberPattern.matcher(parseException.getMessage()); if (m.find()) { /* * if we have already found a broken line, append ", " */ if (lineFound) { buf.append(", "); } /* * we have found a line number -> add it */ buf.append(m.group(1)); lineFound = true; } } if (lineFound) { errors.reject("import.error.erroneous_line_numbers", new Object[] { buf }, "Your submitted publications contain errors at lines {0}."); } } /** * Handles an uploaded file and returns its contents - if necessary * after converting EndNote to BibTeX; * * @param command * @return */ private String handleFileUpload(final PostPublicationCommand command) { /* * get temp file */ File file = null; try { final CommonsMultipartFile uploadedFile = command.getFile(); final FileUploadInterface uploadFileHandler = this.uploadFactory.getFileUploadHandler( Collections.singletonList(uploadedFile.getFileItem()), FileUploadInterface.bibtexEndnoteExt); /* * FIXME: the upload file handler throws an exception, * if the file type does not match - this exception also comes, when * no file is given at all. We should check for empty file names and * give a specific error message then. */ final Document uploadedDocument = uploadFileHandler.writeUploadedFile(); file = uploadedDocument.getFile(); final String fileName = uploadedDocument.getFileName(); final BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(file), command.getEncoding())); String fileContent = null; if (StringUtils.matchExtension(fileName, FileUploadInterface.bibtexEndnoteExt[1])) { /* * In case the uploaded file is in EndNote format, we convert it to BibTeX. */ log.debug("the file is in EndNote format"); fileContent = e2bConverter.endnoteToBibtexString(reader); } else { /* * or just use it as it is ... */ log.debug("the file is in BibTeX format"); fileContent = StringUtils.getStringFromReader(reader); } if (present(fileContent)) { return fileContent; } errors.reject("error.upload.failed.emptyFile", "The specified file is empty."); return null; } catch (final ConversionException e) { errors.reject("error.upload.failed.conversion", "An error occurred during converting your EndNote file to BibTeX."); } catch (final UnsupportedFileTypeException e) { /* * FIXME: the key is missing! We need to get the supported file types from the exception */ errors.reject("error.upload.failed.filetype", FileUploadInterface.bibtexEndnoteExt, e.getMessage()); } catch (final Exception ex1) { errors.reject("error.upload.failed.fileAccess", "An error occurred while accessing your file."); } finally { /* * clear temporary file * FIXME: is this method always called? */ log.debug("deleting uploaded temp file"); if (file != null) { file.delete(); } } return null; } /** * Tries to save the posts in the database. * * If posts already exist in the database and <code>overwrite</code> is <code>true</code>, * those posts are overwritten (otherwise they produce an error). * Posts that have errors will be rejected in any case. * * FIXME: the error handling here is almost identical to that * in {@link BatchEditController#storePosts} * * @param postsToStore * @param overwrite - posts which already exist are overwritten, if <code>true</code> */ private void storePosts(final Map<Post<BibTex>, Integer> postsToStore, final boolean overwrite) { try { /* * Try to save all posts in one transaction. * (Hint: it's not a transaction in the database sense, but basically we try to * save all posts and collect errors for posts we can't save.) * */ logic.createPosts(new LinkedList<Post<?>>(postsToStore.keySet())); } catch (final DatabaseException e) { /* * get error messages */ final Map<String, List<ErrorMessage>> errorMessages = e.getErrorMessages(); log.debug("caught database exception, found " + errorMessages.size() + " errors"); /* * these posts will be updated */ final LinkedList<Post<?>> postsForUpdate = new LinkedList<Post<?>>(); /* * check for all posts what kind of errors they have */ for (final Entry<Post<BibTex>, Integer> entry : postsToStore.entrySet()) { /* * get post and its position in the original list of posts */ final Post<BibTex> post = entry.getKey(); final Integer i = entry.getValue(); log.debug("found errors in post no. " + i); /* * get all error messages for this post */ final List<ErrorMessage> postErrorMessages = errorMessages.get(post.getResource().getIntraHash()); if (present(postErrorMessages)) { boolean hasErrors = false; boolean hasDuplicate = false; /* * go over all error messages */ for (final ErrorMessage errorMessage : postErrorMessages) { log.debug("found error " + errorMessage); final String errorItem; if (errorMessage instanceof DuplicatePostErrorMessage) { hasDuplicate = true; if (overwrite) { /* * if we shall overwrite posts, duplicates are no errors */ continue; } errorItem = "resource"; } else if (errorMessage instanceof SystemTagErrorMessage) { errorItem = "tags"; } else { errorItem = "resource"; } /* * add error to error list */ hasErrors = true; errors.rejectValue("bibtex.list[" + i + "]." + errorItem, errorMessage.getErrorCode(), errorMessage.getParameters(), errorMessage.getDefaultMessage()); } /* * If the post has no errors, but is a duplicate, we add it to * the list of posts which should be updated. */ if (!hasErrors && hasDuplicate) { postsForUpdate.add(post); } } } /* * If we got ONLY duplicate "errors", we save the non-duplicate ones * and update the others, if isOverwrite is true. Same is true, if * the number of publications is greater than the threshold. */ try { if (overwrite) { log.debug("trying to update " + postsForUpdate.size() + " posts"); logic.updatePosts(postsForUpdate, PostUpdateOperation.UPDATE_ALL); } } catch (final DatabaseException ex) { final Map<String, List<ErrorMessage>> allErrorMessages = ex.getErrorMessages(); log.debug("caught database exception, found " + allErrorMessages.size() + " errors"); /* * checking each post for errors */ for (final Post<?> post : postsForUpdate) { /* * get intra hash and original position of post */ final String intraHash = post.getResource().getIntraHash(); /* * The i-th position in the list at hand is not the * same as the i-th position in the original list! ->use mapping */ final int i = postsToStore.get(post); log.debug("checking post no. " + i + " with intra hash " + intraHash); final List<ErrorMessage> postErrorMessages = allErrorMessages.get(intraHash); if (present(postErrorMessages)) { log.debug("found " + postErrorMessages.size() + "error(s) on post no. " + i); for (final ErrorMessage msg : postErrorMessages) { /* * handle system tag error messages */ if (msg instanceof SystemTagErrorMessage) { log.debug("found system tag error"); errors.rejectValue("bibtex.list[" + i + "].tags", msg.getErrorCode(), msg.getParameters(), msg.getDefaultMessage()); } } } } log.debug("all field errors: " + errors.getFieldError("bibtex.*")); } } } @Override protected PostPublicationCommand instantiateEditPostCommand() { return new PostPublicationCommand(); } /** * @return the uploadFactory */ public FileUploadFactory getUploadFactory() { return this.uploadFactory; } /** * @param uploadFactory the uploadFactory to set */ public void setUploadFactory(final FileUploadFactory uploadFactory) { this.uploadFactory = uploadFactory; } }