diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java
deleted file mode 100644
index 487e5a7ebb..0000000000
--- a/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * The Gemma project
- *
- * Copyright (c) 2006 University of British Columbia
- *
- * 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 ubic.gemma.web.controller;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.propertyeditors.CustomNumberEditor;
-import org.springframework.validation.BindException;
-import org.springframework.validation.ObjectError;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.mvc.SimpleFormController;
-import ubic.gemma.core.util.MailEngine;
-import ubic.gemma.web.util.MessageUtil;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.text.NumberFormat;
-import java.util.Locale;
-
-/**
- * Implementation of SimpleFormController that contains convenience methods for subclasses. For
- * example, getting the current user and saving messages/errors. This class is intended to be a base class for all Form
- * controllers.
- * @deprecated {@link SimpleFormController} is deprecated, use annotations-based GET/POST mapping instead.
- *
- * @author pavlidis (originally based on Appfuse code)
- */
-@Deprecated
-public abstract class BaseFormController extends SimpleFormController {
- protected static final Log log = LogFactory.getLog( BaseFormController.class.getName() );
-
- @Autowired
- private MessageUtil messageUtil;
-
- @Autowired
- private MailEngine mailEngine;
-
- /**
- * @return the messageUtil
- */
- public MessageUtil getMessageUtil() {
- return this.messageUtil;
- }
-
- /**
- * @param messageUtil the messageUtil to set
- */
- public void setMessageUtil( MessageUtil messageUtil ) {
- this.messageUtil = messageUtil;
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#getText(java.lang.String, java.util.Locale)
- */
- public String getText( String msgKey, Locale locale ) {
- return this.messageUtil.getText( msgKey, locale );
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#saveMessage(javax.servlet.http.HttpServletRequest, java.lang.String)
- */
- public void saveMessage( HttpServletRequest request, String msg ) {
- this.messageUtil.saveMessage( request, msg );
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#saveMessage(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.Object, java.lang.String)
- */
- public void saveMessage( HttpServletRequest request, String key, Object parameter, String defaultMessage ) {
- this.messageUtil.saveMessage( request, key, parameter, defaultMessage );
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#saveMessage(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.Object[], java.lang.String)
- */
- public void saveMessage( HttpServletRequest request, String key, Object[] parameters, String defaultMessage ) {
- this.messageUtil.saveMessage( request, key, parameters, defaultMessage );
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#saveMessage(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)
- */
- public void saveMessage( HttpServletRequest request, String key, String defaultMessage ) {
- this.messageUtil.saveMessage( request, key, defaultMessage );
- }
-
- /**
- * @see ubic.gemma.web.util.MessageUtilImpl#saveMessage(javax.servlet.http.HttpSession, java.lang.String)
- */
- public void saveMessage( HttpSession session, String msg ) {
- this.messageUtil.saveMessage( session, msg );
- }
-
- public void setMailEngine( MailEngine mailEngine ) {
- this.mailEngine = mailEngine;
- }
-
- /**
- * Override this to control which cancelView is used. The default behavior is to go to the success view if there is
- * no cancel view defined; otherwise, get the cancel view.
- *
- * @param request can be used to control which cancel view to use. (This is not used in the default implementation)
- * @return the view to use.
- */
- protected ModelAndView getCancelView( HttpServletRequest request ) {
- return new ModelAndView( WebConstants.HOME_PAGE );
- }
-
- /**
- * Set up a custom property editor for converting form inputs to real objects. Override this to add additional
- * custom editors (call super.initBinder() in your implementation)
- */
- @InitBinder
- protected void initBinder( WebDataBinder binder ) {
- NumberFormat nf = NumberFormat.getNumberInstance();
- binder.registerCustomEditor( Integer.class, null, new CustomNumberEditor( Integer.class, nf, true ) );
- binder.registerCustomEditor( Long.class, null, new CustomNumberEditor( Long.class, nf, true ) );
- binder.registerCustomEditor( byte[].class, new ByteArrayMultipartFileEditor() );
- }
-
- /**
- * Convenience method to get the user object from the session
- *
- * @param request the current request
- * @return the user's populated object from the session
- */
-
- protected ModelAndView processErrors( HttpServletRequest request, HttpServletResponse response, Object command,
- BindException errors, String message ) throws Exception {
- if ( !StringUtils.isEmpty( message ) ) {
- log.error( message );
- if ( command == null ) {
- errors.addError( new ObjectError( "nullCommand", null, null, message ) );
- } else {
- errors.addError( new ObjectError( command.toString(), null, null, message ) );
- }
- }
-
- return this.processFormSubmission( request, response, command, errors );
- }
-
- /**
- * Default behavior for FormControllers - redirect to the successView when the cancel button has been pressed.
- */
- @Override
- protected ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response,
- Object command, BindException errors ) throws Exception {
- if ( request.getParameter( "cancel" ) != null ) {
- messageUtil.saveMessage( request, "errors.cancel", "Cancelled" );
- return getCancelView( request );
- }
-
- return super.processFormSubmission( request, response, command, errors );
- }
-}
\ No newline at end of file
diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchController.java
index 119839373a..58d4e2fb09 100644
--- a/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchController.java
+++ b/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchController.java
@@ -1,61 +1,393 @@
/*
* The Gemma project
*
- * Copyright (c) 2012 University of British Columbia
+ * Copyright (c) 2007 Columbia University
*
- * 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
+ * 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
+ * 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.
*
- * 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 ubic.gemma.web.controller;
import lombok.Value;
+import lombok.extern.apachecommons.CommonsLog;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.lang3.time.StopWatch;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.propertyeditors.CustomNumberEditor;
+import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
-import org.springframework.validation.BindException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import org.springframework.web.util.UriComponentsBuilder;
+import ubic.gemma.core.search.DefaultHighlighter;
+import ubic.gemma.core.search.SearchException;
import ubic.gemma.core.search.SearchResult;
+import ubic.gemma.core.search.SearchService;
+import ubic.gemma.model.analysis.expression.ExpressionExperimentSet;
+import ubic.gemma.model.blacklist.BlacklistedEntity;
+import ubic.gemma.model.common.Identifiable;
import ubic.gemma.model.common.IdentifiableValueObject;
import ubic.gemma.model.common.search.SearchSettings;
import ubic.gemma.model.common.search.SearchSettingsValueObject;
+import ubic.gemma.model.expression.arrayDesign.ArrayDesign;
+import ubic.gemma.model.expression.designElement.CompositeSequence;
+import ubic.gemma.model.expression.experiment.ExpressionExperiment;
+import ubic.gemma.model.genome.Gene;
+import ubic.gemma.model.genome.Taxon;
+import ubic.gemma.model.genome.gene.GeneSet;
+import ubic.gemma.persistence.service.genome.taxon.TaxonService;
+import ubic.gemma.web.propertyeditor.TaxonPropertyEditor;
import ubic.gemma.web.remote.JsonReaderResponse;
+import ubic.gemma.web.util.MessageUtil;
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import java.net.URI;
+import java.text.NumberFormat;
+import java.util.*;
import java.util.stream.Collectors;
+import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
+import static ubic.gemma.core.search.lucene.LuceneQueryUtils.prepareTermUriQuery;
+
/**
* Note: do not use parametrized collections as parameters for ajax methods in this class! Type information is lost
* during proxy creation so DWR can't figure out what type of collection the method should take. See bug 2756. Use
* arrays instead.
*
- * @author paul
+ * @author klc
*/
+@CommonsLog
@Controller
-public interface GeneralSearchController {
+public class GeneralSearchController {
/**
- * AJAX-flavoured search.
- * Mapped by DWR.
+ * Maximum number of highlighted documents.
*/
- @SuppressWarnings("unused")
- JsonReaderResponse> ajaxSearch( SearchSettingsValueObject settings );
+ private static final int MAX_HIGHLIGHTED_DOCUMENTS = 500;
+
+ @Value
+ private static class Scope {
+ char scope;
+ Class extends Identifiable> resultType;
+ }
+
+ /**
+ * List of supported scopes (or result types) for searching.
+ */
+ private static final Scope[] scopes = {
+ new Scope( 'G', Gene.class ),
+ new Scope( 'E', ExpressionExperiment.class ),
+ new Scope( 'P', CompositeSequence.class ),
+ new Scope( 'A', ArrayDesign.class ),
+ new Scope( 'M', GeneSet.class ),
+ new Scope( 'N', ExpressionExperimentSet.class )
+ };
+
+ @Autowired
+ private SearchService searchService;
+ @Autowired
+ private TaxonService taxonService;
+ @Autowired
+ private MessageSource messageSource;
+ @Autowired
+ private MessageUtil messageUtil;
+
+ @Autowired
+ private HttpServletRequest request;
+
+ /**
+ * Set up a custom property editor for converting form inputs to real objects. Override this to add additional
+ * custom editors (call super.initBinder() in your implementation)
+ */
+ @InitBinder
+ protected void initBinder( WebDataBinder binder ) {
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ binder.registerCustomEditor( Integer.class, null, new CustomNumberEditor( Integer.class, nf, true ) );
+ binder.registerCustomEditor( Long.class, null, new CustomNumberEditor( Long.class, nf, true ) );
+ binder.registerCustomEditor( byte[].class, new ByteArrayMultipartFileEditor() );
+ binder.registerCustomEditor( Taxon.class, new TaxonPropertyEditor( this.taxonService ) );
+ }
+
+ @RequestMapping(value = "/searcher.html", method = RequestMethod.GET)
+ public ModelAndView showForm( HttpServletRequest request ) {
+ if ( request.getParameter( "query" ) != null || request.getParameter( "termUri" ) != null ) {
+ return this.doSearch( request );
+ }
+
+ return new ModelAndView( "generalSearch" );
+ }
@RequestMapping(value = "/searcher.html", method = RequestMethod.POST)
- ModelAndView doSearch( HttpServletRequest request, HttpServletResponse response, SearchSettings command,
- BindException errors ) throws Exception;
+ public ModelAndView doSearch( HttpServletRequest request ) {
+ SearchSettings command = formBackingObject( request );
+
+ command.setQuery( StringUtils.trim( command.getQuery().trim() ) );
+
+ ModelAndView mav = new ModelAndView( "generalSearch" );
+
+ if ( !searchStringValidator( command.getQuery() ) ) {
+ throw new IllegalArgumentException( "Invalid query" );
+ }
+
+ // Need this for the bookmarkable links
+ mav.addObject( "SearchString", command.getQuery() );
+ try {
+ URI termUri = prepareTermUriQuery( command );
+ mav.addObject( "SearchURI", termUri != null ? termUri.toString() : null );
+ } catch ( SearchException e ) {
+ mav.addObject( "SearchURI", null );
+ }
+ if ( ( command.getTaxon() != null ) && ( command.getTaxon().getId() != null ) )
+ mav.addObject( "searchTaxon", command.getTaxon().getScientificName() );
+
+ return mav;
+ }
+
+ @SuppressWarnings("unused")
+ public JsonReaderResponse> ajaxSearch( SearchSettingsValueObject settingsValueObject ) {
+ StopWatch timer = new StopWatch();
+ StopWatch searchTimer = new StopWatch();
+ StopWatch fillVosTimer = new StopWatch();
+
+ if ( settingsValueObject == null || StringUtils.isBlank( settingsValueObject.getQuery() ) || StringUtils
+ .isBlank( settingsValueObject.getQuery().replaceAll( "\\*", "" ) ) ) {
+ // FIXME validate input better, and return error.
+ GeneralSearchController.log.info( "No query or invalid." );
+ // return new ListRange( finalResults );
+ throw new IllegalArgumentException( "Query '" + settingsValueObject + "' was invalid" );
+ }
+
+ timer.start();
+
+ SearchSettings searchSettings = searchSettingsFromVo( settingsValueObject )
+ .withHighlighter( new Highlighter( scopeFromVo( settingsValueObject ), request.getLocale() ) );
+
+ searchTimer.start();
+ SearchService.SearchResultMap searchResults;
+ try {
+ searchResults = searchService.search( searchSettings );
+ } catch ( SearchException e ) {
+ throw new IllegalArgumentException( String.format( "Invalid search settings: %s.", ExceptionUtils.getRootCause( e ) ), e );
+ }
+ searchTimer.stop();
+
+ // FIXME: sort by the number of hits per class, so the smallest number of hits is at the top.
+ fillVosTimer.start();
+ List> finalResults = new ArrayList<>();
+ for ( Class extends Identifiable> clazz : searchResults.getResultTypes() ) {
+ List> results = searchResults.getByResultType( clazz );
+
+ if ( results.size() == 0 )
+ continue;
+
+ GeneralSearchController.log.debug( String.format( "Search for: %s; result: %d %ss",
+ searchSettings, results.size(), clazz.getSimpleName() ) );
+
+ /*
+ * Now put the valueObjects inside the SearchResults in score order.
+ */
+ searchService.loadValueObjects( results ).stream()
+ .sorted()
+ .map( SearchResultValueObject::new )
+ .forEachOrdered( finalResults::add );
+ }
+
+ fillVosTimer.stop();
+
+ timer.stop();
+
+ if ( timer.getTime() > 500 ) {
+ GeneralSearchController.log
+ .warn( "Searching for query: " + searchSettings + " took " + timer.getTime() + " ms ("
+ + "searching: " + searchTimer.getTime() + " ms, "
+ + "filling VOs: " + fillVosTimer.getTime() + " ms)." );
+ }
- ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response,
- SearchSettings command, BindException errors ) throws Exception;
+ return new JsonReaderResponse<>( finalResults );
+ }
+
+ @ParametersAreNonnullByDefault
+ private class Highlighter extends DefaultHighlighter {
+
+ @Nullable
+ private final String scope;
+ private final Locale locale;
+
+ private int highlightedDocuments = 0;
+
+ private Highlighter( @Nullable String scope, Locale locale ) {
+ this.scope = scope;
+ this.locale = locale;
+ }
+
+ @Override
+ public Map highlightTerm( @Nullable String uri, String value, String field ) {
+ // some of the incoming requests are from AJAX, so we cannot use fromRequest
+ UriComponentsBuilder builder = ServletUriComponentsBuilder.fromContextPath( request )
+ .scheme( null ).host( null ).port( -1 )
+ .path( "/searcher.html" )
+ .queryParam( "query", uri != null ? uri : value );
+ if ( scope != null ) {
+ builder.queryParam( "scope", scope );
+ }
+ String searchUrl = builder.build().toUriString();
+ String matchedText = "" + escapeHtml4( value ) + " ";
+ return Collections.singletonMap( localizeField( "ExpressionExperiment", field ), matchedText );
+ }
+
+ @Override
+ public Map highlightDocument( Document document, org.apache.lucene.search.highlight.Highlighter highlighter, Analyzer analyzer ) {
+ if ( highlightedDocuments >= MAX_HIGHLIGHTED_DOCUMENTS ) {
+ return Collections.emptyMap();
+ }
+ highlightedDocuments++;
+ return super.highlightDocument( document, highlighter, analyzer )
+ .entrySet().stream()
+ .collect( Collectors.toMap( e -> localizeField( StringUtils.substringAfterLast( document.get( "_hibernate_class" ), '.' ), e.getKey() ), Map.Entry::getValue, ( a, b ) -> b ) );
+ }
+
+ private String localizeField( String className, String field ) {
+ return messageSource.getMessage( className + "." + field, null, field, locale );
+ }
+ }
+
+ /**
+ * This is needed or you will have to specify a commandClass in the DispatcherServlet's context
+ */
+ protected SearchSettings formBackingObject( HttpServletRequest request ) {
+ SearchSettings.SearchSettingsBuilder csc = SearchSettings.builder();
+ csc.query( !StringUtils.isBlank( request.getParameter( "query" ) ) ? request.getParameter( "query" ) : request.getParameter( "termUri" ) );
+ String taxon = request.getParameter( "taxon" );
+ if ( taxon != null )
+ csc.taxon( taxonService.findByScientificName( taxon ) );
+ String scope = request.getParameter( "scope" );
+ csc.highlighter( new Highlighter( scope, request.getLocale() ) );
+ if ( StringUtils.isNotBlank( scope ) ) {
+ char[] scopes = scope.toCharArray();
+ for ( char s : scopes ) {
+ boolean found = false;
+ for ( Scope s2 : GeneralSearchController.scopes ) {
+ if ( s2.scope == s ) {
+ csc.resultType( s2.resultType );
+ found = true;
+ break;
+ }
+ }
+ if ( !found ) {
+ throw new IllegalArgumentException( String.format( "Unsupported value for scope: %c.", s ) );
+ }
+ }
+ }
+ return csc.build();
+ }
+
+ protected Map> referenceData( HttpServletRequest request ) {
+ Map> mapping = new HashMap<>();
+
+ // add species
+ this.populateTaxonReferenceData( mapping );
+
+ return mapping;
+ }
+
+ // private Collection filterEE(
+ // final Collection toFilter, SearchSettings settings ) {
+ // Taxon tax = settings.getTaxon();
+ // if ( tax == null )
+ // return toFilter;
+ // Collection filtered = new HashSet<>();
+ // for ( ExpressionExperimentValueObject eevo : toFilter ) {
+ // if ( eevo.getTaxon().equalsIgnoreCase( tax.getCommonName() ) )
+ // filtered.add( eevo );
+ // }
+ //
+ // return filtered;
+ // }
+
+ private void populateTaxonReferenceData( Map> mapping ) {
+ List taxa = new ArrayList<>( taxonService.loadAll() );
+ taxa.sort( Comparator.comparing( Taxon::getScientificName ) );
+ mapping.put( "taxa", taxa );
+ }
+
+ private static boolean searchStringValidator( String query ) {
+ return !StringUtils.isBlank( query ) && !( ( query.charAt( 0 ) == '%' ) || ( query.charAt( 0 ) == '*' ) );
+ }
+
+ private static SearchSettings searchSettingsFromVo( SearchSettingsValueObject settingsValueObject ) {
+ return SearchSettings.builder()
+ .query( !StringUtils.isBlank( settingsValueObject.getQuery() ) ? settingsValueObject.getQuery() : settingsValueObject.getTermUri() )
+ .platformConstraint( settingsValueObject.getPlatformConstraint() )
+ .taxon( settingsValueObject.getTaxon() )
+ .maxResults( settingsValueObject.getMaxResults() != null ? settingsValueObject.getMaxResults() : SearchSettings.DEFAULT_MAX_RESULTS_PER_RESULT_TYPE )
+ .resultTypes( resultTypesFromVo( settingsValueObject ) )
+ .resultType( BlacklistedEntity.class )
+ .useIndices( settingsValueObject.getUseIndices() )
+ .useDatabase( settingsValueObject.getUseDatabase() )
+ .useCharacteristics( settingsValueObject.getUseCharacteristics() )
+ .useGo( settingsValueObject.getUseGo() )
+ .build();
+ }
+
+ private String scopeFromVo( SearchSettingsValueObject settingsValueObject ) {
+ Set> resultTypes = resultTypesFromVo( settingsValueObject );
+ StringBuilder scope = new StringBuilder();
+ for ( Class extends Identifiable> resultType : resultTypes ) {
+ for ( Scope s : scopes ) {
+ if ( resultType.equals( s.resultType ) ) {
+ scope.append( s.scope );
+ break;
+ }
+ }
+ }
+ return scope.toString();
+ }
+
+ private static Set> resultTypesFromVo( SearchSettingsValueObject valueObject ) {
+ Set> ret = new HashSet<>();
+ if ( valueObject.getSearchExperiments() ) {
+ ret.add( ExpressionExperiment.class );
+ }
+ if ( valueObject.getSearchGenes() ) {
+ ret.add( Gene.class );
+ }
+ if ( valueObject.getSearchPlatforms() ) {
+ ret.add( ArrayDesign.class );
+ }
+ if ( valueObject.getSearchExperimentSets() ) {
+ ret.add( ExpressionExperimentSet.class );
+ }
+ if ( valueObject.getSearchProbes() ) {
+ ret.add( CompositeSequence.class );
+ }
+ if ( valueObject.getSearchGeneSets() ) {
+ ret.add( GeneSet.class );
+ }
+ return ret;
+ }
@Value
- class SearchResultValueObject> {
+ public static class SearchResultValueObject> {
Class> resultClass;
double score;
@@ -75,4 +407,4 @@ public SearchResultValueObject( SearchResult result ) {
this.resultObject = result.getResultObject();
}
}
-}
\ No newline at end of file
+}
diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchControllerImpl.java b/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchControllerImpl.java
deleted file mode 100644
index e4a2549f08..0000000000
--- a/gemma-web/src/main/java/ubic/gemma/web/controller/GeneralSearchControllerImpl.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * The Gemma project
- *
- * Copyright (c) 2007 Columbia University
- *
- * 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 ubic.gemma.web.controller;
-
-import lombok.Value;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.apache.commons.lang3.time.StopWatch;
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.document.Document;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.MessageSource;
-import org.springframework.stereotype.Controller;
-import org.springframework.validation.BindException;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
-import org.springframework.web.servlet.view.RedirectView;
-import org.springframework.web.util.UriComponentsBuilder;
-import ubic.gemma.core.search.DefaultHighlighter;
-import ubic.gemma.core.search.SearchException;
-import ubic.gemma.core.search.SearchResult;
-import ubic.gemma.core.search.SearchService;
-import ubic.gemma.model.analysis.expression.ExpressionExperimentSet;
-import ubic.gemma.model.common.Identifiable;
-import ubic.gemma.model.common.search.SearchSettings;
-import ubic.gemma.model.common.search.SearchSettingsValueObject;
-import ubic.gemma.model.blacklist.BlacklistedEntity;
-import ubic.gemma.model.expression.arrayDesign.ArrayDesign;
-import ubic.gemma.model.expression.designElement.CompositeSequence;
-import ubic.gemma.model.expression.experiment.ExpressionExperiment;
-import ubic.gemma.model.genome.Gene;
-import ubic.gemma.model.genome.Taxon;
-import ubic.gemma.model.genome.gene.GeneSet;
-import ubic.gemma.persistence.service.genome.taxon.TaxonService;
-import ubic.gemma.web.propertyeditor.TaxonPropertyEditor;
-import ubic.gemma.web.remote.JsonReaderResponse;
-
-import javax.annotation.Nullable;
-import javax.annotation.ParametersAreNonnullByDefault;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.net.URI;
-import java.util.*;
-import java.util.stream.Collectors;
-
-import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
-import static ubic.gemma.core.search.lucene.LuceneQueryUtils.prepareTermUriQuery;
-
-/**
- * Note: do not use parametrized collections as parameters for ajax methods in this class! Type information is lost
- * during proxy creation so DWR can't figure out what type of collection the method should take. See bug 2756. Use
- * arrays instead.
- *
- * @author klc
- */
-@Controller
-public class GeneralSearchControllerImpl extends BaseFormController implements GeneralSearchController {
-
- /**
- * Maximum number of highlighted documents.
- */
- private static final int MAX_HIGHLIGHTED_DOCUMENTS = 500;
-
- @Value
- private static class Scope {
- char scope;
- Class extends Identifiable> resultType;
- }
-
- /**
- * List of supported scopes (or result types) for searching.
- */
- private static final Scope[] scopes = {
- new Scope( 'G', Gene.class ),
- new Scope( 'E', ExpressionExperiment.class ),
- new Scope( 'P', CompositeSequence.class ),
- new Scope( 'A', ArrayDesign.class ),
- new Scope( 'M', GeneSet.class ),
- new Scope( 'N', ExpressionExperimentSet.class )
- };
-
- @Autowired
- private SearchService searchService;
- @Autowired
- private TaxonService taxonService;
- @Autowired
- private MessageSource messageSource;
-
- @Autowired
- private HttpServletRequest request;
-
- @Override
- public JsonReaderResponse> ajaxSearch( SearchSettingsValueObject settingsValueObject ) {
- StopWatch timer = new StopWatch();
- StopWatch searchTimer = new StopWatch();
- StopWatch fillVosTimer = new StopWatch();
-
- if ( settingsValueObject == null || StringUtils.isBlank( settingsValueObject.getQuery() ) || StringUtils
- .isBlank( settingsValueObject.getQuery().replaceAll( "\\*", "" ) ) ) {
- // FIXME validate input better, and return error.
- BaseFormController.log.info( "No query or invalid." );
- // return new ListRange( finalResults );
- throw new IllegalArgumentException( "Query '" + settingsValueObject + "' was invalid" );
- }
-
- timer.start();
-
- SearchSettings searchSettings = searchSettingsFromVo( settingsValueObject )
- .withHighlighter( new Highlighter( scopeFromVo( settingsValueObject ), request.getLocale() ) );
-
- searchTimer.start();
- SearchService.SearchResultMap searchResults;
- try {
- searchResults = searchService.search( searchSettings );
- } catch ( SearchException e ) {
- throw new IllegalArgumentException( String.format( "Invalid search settings: %s.", ExceptionUtils.getRootCause( e ) ), e );
- }
- searchTimer.stop();
-
- // FIXME: sort by the number of hits per class, so the smallest number of hits is at the top.
- fillVosTimer.start();
- List> finalResults = new ArrayList<>();
- for ( Class extends Identifiable> clazz : searchResults.getResultTypes() ) {
- List> results = searchResults.getByResultType( clazz );
-
- if ( results.size() == 0 )
- continue;
-
- BaseFormController.log.debug( String.format( "Search for: %s; result: %d %ss",
- searchSettings, results.size(), clazz.getSimpleName() ) );
-
- /*
- * Now put the valueObjects inside the SearchResults in score order.
- */
- searchService.loadValueObjects( results ).stream()
- .sorted()
- .map( SearchResultValueObject::new )
- .forEachOrdered( finalResults::add );
- }
-
- fillVosTimer.stop();
-
- timer.stop();
-
- if ( timer.getTime() > 500 ) {
- BaseFormController.log
- .warn( "Searching for query: " + searchSettings + " took " + timer.getTime() + " ms ("
- + "searching: " + searchTimer.getTime() + " ms, "
- + "filling VOs: " + fillVosTimer.getTime() + " ms)." );
- }
-
- return new JsonReaderResponse<>( finalResults );
- }
-
- @ParametersAreNonnullByDefault
- private class Highlighter extends DefaultHighlighter {
-
- @Nullable
- private final String scope;
- private final Locale locale;
-
- private int highlightedDocuments = 0;
-
- private Highlighter( @Nullable String scope, Locale locale ) {
- this.scope = scope;
- this.locale = locale;
- }
-
- @Override
- public Map highlightTerm( @Nullable String uri, String value, String field ) {
- // some of the incoming requests are from AJAX, so we cannot use fromRequest
- UriComponentsBuilder builder = ServletUriComponentsBuilder.fromContextPath( request )
- .scheme( null ).host( null ).port( -1 )
- .path( "/searcher.html" )
- .queryParam( "query", uri != null ? uri : value );
- if ( scope != null ) {
- builder.queryParam( "scope", scope );
- }
- String searchUrl = builder.build().toUriString();
- String matchedText = "" + escapeHtml4( value ) + " ";
- return Collections.singletonMap( localizeField( "ExpressionExperiment", field ), matchedText );
- }
-
- @Override
- public Map highlightDocument( Document document, org.apache.lucene.search.highlight.Highlighter highlighter, Analyzer analyzer ) {
- if ( highlightedDocuments >= MAX_HIGHLIGHTED_DOCUMENTS ) {
- return Collections.emptyMap();
- }
- highlightedDocuments++;
- return super.highlightDocument( document, highlighter, analyzer )
- .entrySet().stream()
- .collect( Collectors.toMap( e -> localizeField( StringUtils.substringAfterLast( document.get( "_hibernate_class" ), '.' ), e.getKey() ), Map.Entry::getValue, ( a, b ) -> b ) );
- }
-
- private String localizeField( String className, String field ) {
- return messageSource.getMessage( className + "." + field, null, field, locale );
- }
- }
-
- @Override
- public ModelAndView doSearch( HttpServletRequest request, HttpServletResponse response, SearchSettings command,
- BindException errors ) {
-
- command.setQuery( StringUtils.trim( command.getQuery().trim() ) );
-
- ModelAndView mav = new ModelAndView( "generalSearch" );
-
- if ( !searchStringValidator( command.getQuery() ) ) {
- throw new IllegalArgumentException( "Invalid query" );
- }
-
- // Need this for the bookmarkable links
- mav.addObject( "SearchString", command.getQuery() );
- try {
- URI termUri = prepareTermUriQuery( command );
- mav.addObject( "SearchURI", termUri != null ? termUri.toString() : null );
- } catch ( SearchException e ) {
- mav.addObject( "SearchURI", null );
- }
- if ( ( command.getTaxon() != null ) && ( command.getTaxon().getId() != null ) )
- mav.addObject( "searchTaxon", command.getTaxon().getScientificName() );
-
- return mav;
- }
-
- @Override
- public ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response,
- SearchSettings command, BindException errors ) throws Exception {
-
- if ( request.getParameter( "query" ) != null ) {
- ModelAndView mav = new ModelAndView();
- mav.addObject( "query", request.getParameter( "query" ) );
- return mav;
- }
-
- if ( request.getParameter( "cancel" ) != null ) {
- this.saveMessage( request, "Cancelled Search" );
- return new ModelAndView( new RedirectView( WebConstants.HOME_PAGE, true ) );
- }
-
- return this.doSearch( request, response, command, errors );
- }
-
- /**
- * This is needed or you will have to specify a commandClass in the DispatcherServlet's context
- */
- @Override
- protected SearchSettings formBackingObject( HttpServletRequest request ) {
- SearchSettings.SearchSettingsBuilder csc = SearchSettings.builder();
- csc.query( !StringUtils.isBlank( request.getParameter( "query" ) ) ? request.getParameter( "query" ) : request.getParameter( "termUri" ) );
- String taxon = request.getParameter( "taxon" );
- if ( taxon != null )
- csc.taxon( taxonService.findByScientificName( taxon ) );
- String scope = request.getParameter( "scope" );
- csc.highlighter( new Highlighter( scope, request.getLocale() ) );
- if ( StringUtils.isNotBlank( scope ) ) {
- char[] scopes = scope.toCharArray();
- for ( char s : scopes ) {
- boolean found = false;
- for ( Scope s2 : GeneralSearchControllerImpl.scopes ) {
- if ( s2.scope == s ) {
- csc.resultType( s2.resultType );
- found = true;
- break;
- }
- }
- if ( !found ) {
- throw new IllegalArgumentException( String.format( "Unsupported value for scope: %c.", s ) );
- }
- }
- }
- return csc.build();
- }
-
- @Override
- @InitBinder
- protected void initBinder( WebDataBinder binder ) {
- super.initBinder( binder );
- binder.registerCustomEditor( Taxon.class, new TaxonPropertyEditor( this.taxonService ) );
- }
-
- @Deprecated
- @Override
- @RequestMapping(value = "/searcher.html", method = RequestMethod.GET)
- public ModelAndView showForm( HttpServletRequest request, HttpServletResponse response, BindException errors ) {
- if ( request.getParameter( "query" ) != null || request.getParameter( "termUri" ) != null ) {
- SearchSettings searchSettings = this.formBackingObject( request );
- return this.doSearch( request, response, searchSettings, errors );
- }
-
- return new ModelAndView( "generalSearch" );
- }
-
- @Override
- protected Map> referenceData( HttpServletRequest request ) {
- Map> mapping = new HashMap<>();
-
- // add species
- this.populateTaxonReferenceData( mapping );
-
- return mapping;
- }
-
- // private Collection filterEE(
- // final Collection toFilter, SearchSettings settings ) {
- // Taxon tax = settings.getTaxon();
- // if ( tax == null )
- // return toFilter;
- // Collection filtered = new HashSet<>();
- // for ( ExpressionExperimentValueObject eevo : toFilter ) {
- // if ( eevo.getTaxon().equalsIgnoreCase( tax.getCommonName() ) )
- // filtered.add( eevo );
- // }
- //
- // return filtered;
- // }
-
- private void populateTaxonReferenceData( Map> mapping ) {
- List taxa = new ArrayList<>( taxonService.loadAll() );
- taxa.sort( Comparator.comparing( Taxon::getScientificName ) );
- mapping.put( "taxa", taxa );
- }
-
- private static boolean searchStringValidator( String query ) {
- return !StringUtils.isBlank( query ) && !( ( query.charAt( 0 ) == '%' ) || ( query.charAt( 0 ) == '*' ) );
- }
-
- private static SearchSettings searchSettingsFromVo( SearchSettingsValueObject settingsValueObject ) {
- return SearchSettings.builder()
- .query( !StringUtils.isBlank( settingsValueObject.getQuery() ) ? settingsValueObject.getQuery() : settingsValueObject.getTermUri() )
- .platformConstraint( settingsValueObject.getPlatformConstraint() )
- .taxon( settingsValueObject.getTaxon() )
- .maxResults( settingsValueObject.getMaxResults() != null ? settingsValueObject.getMaxResults() : SearchSettings.DEFAULT_MAX_RESULTS_PER_RESULT_TYPE )
- .resultTypes( resultTypesFromVo( settingsValueObject ) )
- .resultType( BlacklistedEntity.class )
- .useIndices( settingsValueObject.getUseIndices() )
- .useDatabase( settingsValueObject.getUseDatabase() )
- .useCharacteristics( settingsValueObject.getUseCharacteristics() )
- .useGo( settingsValueObject.getUseGo() )
- .build();
- }
-
- private String scopeFromVo( SearchSettingsValueObject settingsValueObject ) {
- Set> resultTypes = resultTypesFromVo( settingsValueObject );
- StringBuilder scope = new StringBuilder();
- for ( Class extends Identifiable> resultType : resultTypes ) {
- for ( Scope s : scopes ) {
- if ( resultType.equals( s.resultType ) ) {
- scope.append( s.scope );
- break;
- }
- }
- }
- return scope.toString();
- }
-
- private static Set> resultTypesFromVo( SearchSettingsValueObject valueObject ) {
- Set> ret = new HashSet<>();
- if ( valueObject.getSearchExperiments() ) {
- ret.add( ExpressionExperiment.class );
- }
- if ( valueObject.getSearchGenes() ) {
- ret.add( Gene.class );
- }
- if ( valueObject.getSearchPlatforms() ) {
- ret.add( ArrayDesign.class );
- }
- if ( valueObject.getSearchExperimentSets() ) {
- ret.add( ExpressionExperimentSet.class );
- }
- if ( valueObject.getSearchProbes() ) {
- ret.add( CompositeSequence.class );
- }
- if ( valueObject.getSearchGeneSets() ) {
- ret.add( GeneSet.class );
- }
- return ret;
- }
-}
diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignEditController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignEditController.java
new file mode 100644
index 0000000000..abb1cd0922
--- /dev/null
+++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignEditController.java
@@ -0,0 +1,120 @@
+/*
+ * The Gemma project
+ *
+ * Copyright (c) 2006 University of British Columbia
+ *
+ * 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 ubic.gemma.web.controller.expression.arrayDesign;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.propertyeditors.CustomNumberEditor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
+import ubic.gemma.model.expression.arrayDesign.ArrayDesign;
+import ubic.gemma.model.expression.arrayDesign.ArrayDesignValueObject;
+import ubic.gemma.model.expression.arrayDesign.TechnologyType;
+import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService;
+import ubic.gemma.web.util.EntityNotFoundException;
+import ubic.gemma.web.util.MessageUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Controller for editing basic information about array designs.
+ *
+ * @author keshav
+ */
+@Controller
+public class ArrayDesignEditController {
+
+ private static final List TECHNOLOGY_TYPES = Arrays.stream( TechnologyType.values() )
+ .map( TechnologyType::name )
+ .sorted()
+ .collect( Collectors.toList() );
+
+ @Autowired
+ private ArrayDesignService arrayDesignService;
+
+ @Autowired
+ private MessageUtil messageUtil;
+
+ /**
+ * Set up a custom property editor for converting form inputs to real objects. Override this to add additional
+ * custom editors (call super.initBinder() in your implementation)
+ */
+ @InitBinder
+ protected void initBinder( WebDataBinder binder ) {
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ binder.registerCustomEditor( Integer.class, null, new CustomNumberEditor( Integer.class, nf, true ) );
+ binder.registerCustomEditor( Long.class, null, new CustomNumberEditor( Long.class, nf, true ) );
+ binder.registerCustomEditor( byte[].class, new ByteArrayMultipartFileEditor() );
+ }
+
+ @RequestMapping(value = "/arrayDesign/editArrayDesign.html", method = RequestMethod.GET)
+ public ModelAndView getArrayDesign( @RequestParam("id") Long id ) {
+ return new ModelAndView( "arrayDesign.edit" )
+ .addObject( "arrayDesign", formBackingObject( id ) )
+ .addObject( "technologyTypes", TECHNOLOGY_TYPES );
+ }
+
+ @RequestMapping(value = "/arrayDesign/editArrayDesign.html", method = RequestMethod.POST)
+ public ModelAndView updateArrayDesign( ArrayDesignValueObject ad, HttpServletRequest request ) {
+ ArrayDesign existing = arrayDesignService.loadOrFail( ad.getId(), EntityNotFoundException::new, "No platform with ID " + ad.getId() );
+
+ // existing = arrayDesignService.thawLite( existing );
+ existing.setDescription( ad.getDescription() );
+ existing.setName( ad.getName() );
+ existing.setShortName( ad.getShortName() );
+ String technologyType = ad.getTechnologyType();
+ if ( StringUtils.isNotBlank( technologyType ) ) {
+ existing.setTechnologyType( TechnologyType.valueOf( technologyType ) );
+ }
+
+ arrayDesignService.update( existing );
+
+ messageUtil.saveMessage( request, "object.updated",
+ new Object[] { ad.getClass().getSimpleName().replaceFirst( "Impl", "" ), ad.getName() }, "Saved" );
+
+ // go back to the array we just edited.
+ return new ModelAndView( new RedirectView( "/arrays/showArrayDesign.html?id=" + ad.getId(), true ) );
+ }
+
+ /**
+ * Case = GET: Step 1 - return instance of command class (from database). This is not called in the POST case
+ * because the sessionForm is set to 'true' in the constructor. This means the command object was already bound to
+ * the session in the GET case.
+ *
+ * @return Object
+ */
+ protected Object formBackingObject( Long id ) {
+ ArrayDesignValueObject arrayDesign = arrayDesignService.loadValueObjectById( id );
+ if ( arrayDesign == null ) {
+ throw new EntityNotFoundException( "No platform with ID " + id );
+ }
+ return arrayDesign;
+ }
+}
diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java
deleted file mode 100644
index a5ad4e0c66..0000000000
--- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * The Gemma project
- *
- * Copyright (c) 2006 University of British Columbia
- *
- * 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 ubic.gemma.web.controller.expression.arrayDesign;
-
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.validation.BindException;
-import org.springframework.validation.ObjectError;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.view.RedirectView;
-import ubic.gemma.model.expression.arrayDesign.ArrayDesign;
-import ubic.gemma.model.expression.arrayDesign.ArrayDesignValueObject;
-import ubic.gemma.model.expression.arrayDesign.TechnologyType;
-import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService;
-import ubic.gemma.web.controller.BaseFormController;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.util.*;
-import java.util.stream.Collectors;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * Controller for editing basic information about array designs.
- *
- * @author keshav
- */
-public class ArrayDesignFormController extends BaseFormController {
-
- private ArrayDesignService arrayDesignService = null;
-
- @Override
- public ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command,
- BindException errors ) throws Exception {
- ArrayDesignValueObject ad = ( ArrayDesignValueObject ) command;
-
- ArrayDesign existing = arrayDesignService.load( ad.getId() );
-
- if ( existing == null ) {
- errors.addError(
- new ObjectError( command.toString(), null, null, "No such platform with id=" + ad.getId() ) );
- return processFormSubmission( request, response, command, errors );
- }
-
- // existing = arrayDesignService.thawLite( existing );
- existing.setDescription( ad.getDescription() );
- existing.setName( ad.getName() );
- existing.setShortName( ad.getShortName() );
- String technologyType = ad.getTechnologyType();
- if ( StringUtils.isNotBlank( technologyType ) ) {
- existing.setTechnologyType( TechnologyType.valueOf( technologyType ) );
- }
-
- arrayDesignService.update( existing );
-
- saveMessage( request, "object.updated",
- new Object[] { ad.getClass().getSimpleName().replaceFirst( "Impl", "" ), ad.getName() }, "Saved" );
-
- // go back to the aray we just edited.
- return new ModelAndView( new RedirectView( "/arrays/showArrayDesign.html?id=" + ad.getId(), true ) );
- }
-
- @Override
- public ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response, Object command,
- BindException errors ) throws Exception {
-
- log.debug( "entering processFormSubmission" );
-
- return super.processFormSubmission( request, response, command, errors );
- }
-
- public void setArrayDesignService( ArrayDesignService arrayDesignService ) {
- this.arrayDesignService = arrayDesignService;
- }
-
- /**
- * Case = GET: Step 1 - return instance of command class (from database). This is not called in the POST case
- * because the sessionForm is set to 'true' in the constructor. This means the command object was already bound to
- * the session in the GET case.
- *
- * @param request http request
- * @return Object
- */
- @Override
- protected Object formBackingObject( HttpServletRequest request ) {
-
- String idString = request.getParameter( "id" );
-
- Long id;
- ArrayDesignValueObject arrayDesign = null;
-
- // should be caught by validation.
- if ( idString != null ) {
- try {
- id = Long.parseLong( idString );
- } catch ( NumberFormatException e ) {
- throw new IllegalArgumentException( "Invalid ID for platform.", e );
- }
- Collection ids = new HashSet();
- ids.add( id );
- Collection arrayDesigns = arrayDesignService.loadValueObjectsByIds( ids );
- if ( arrayDesigns.size() > 0 )
- arrayDesign = arrayDesigns.iterator().next();
-
- }
-
- if ( arrayDesign == null ) {
- return new ArrayDesignValueObject( -1L );
- }
- return arrayDesign;
- }
-
- @Override
- protected ModelAndView getCancelView( HttpServletRequest request ) {
- long id = Long.parseLong( requireNonNull( request.getParameter( "id" ),
- "The 'id' query parameter is required." ) );
- // go back to the aray we just edited.
- return new ModelAndView(
- new RedirectView( "/arrays/showArrayDesign.html?id=" + id, true ) );
- }
-
- private static final List TECHNOLOGY_TYPES = Arrays.stream( TechnologyType.values() )
- .map( TechnologyType::name )
- .sorted()
- .collect( Collectors.toList() );
-
- @Override
- protected Map> referenceData( HttpServletRequest request ) {
- Map> mapping = new HashMap<>();
- mapping.put( "technologyTypes", new ArrayList<>( TECHNOLOGY_TYPES ) );
- return mapping;
- }
-
-}
diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentEditController.java
similarity index 71%
rename from gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java
rename to gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentEditController.java
index 99b2dea2cb..a4194a8e15 100644
--- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java
+++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentEditController.java
@@ -19,15 +19,22 @@
package ubic.gemma.web.controller.expression.experiment;
import gemma.gsec.util.SecurityUtil;
-import org.apache.commons.text.StringEscapeUtils;
+import lombok.extern.apachecommons.CommonsLog;
import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.security.access.AccessDeniedException;
-import org.springframework.validation.BindException;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import ubic.gemma.core.analysis.preprocess.PreprocessingException;
import ubic.gemma.core.analysis.preprocess.PreprocessorService;
-import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType;
import ubic.gemma.model.common.auditAndSecurity.eventType.BioMaterialMappingUpdate;
import ubic.gemma.model.common.description.DatabaseType;
import ubic.gemma.model.common.description.ExternalDatabase;
@@ -44,63 +51,79 @@
import ubic.gemma.persistence.service.expression.bioAssay.BioAssayService;
import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService;
import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService;
-import ubic.gemma.web.controller.BaseFormController;
import ubic.gemma.web.util.EntityNotFoundException;
+import ubic.gemma.web.util.MessageUtil;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import java.text.NumberFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
+import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
+
/**
* Handle editing of expression experiments.
*
* @author keshav
*/
-public class ExpressionExperimentFormController extends BaseFormController {
+@CommonsLog
+@Controller
+public class ExpressionExperimentEditController {
- private AuditTrailService auditTrailService;
- private BioAssayService bioAssayService = null;
- private BioMaterialService bioMaterialService = null;
- private ExpressionExperimentService expressionExperimentService = null;
- private ExternalDatabaseService externalDatabaseService = null;
- private Persister persisterHelper = null;
+ private static final List
+ STANDARD_QUANTITATION_TYPES = Arrays.stream( StandardQuantitationType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
+ SCALE_TYPES = Arrays.stream( ScaleType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
+ GENERAL_QUANTITATION_TYPES = Arrays.stream( GeneralType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
+ REPRESENTATIONS = Arrays.stream( PrimitiveType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() );
+ @Autowired
+ private AuditTrailService auditTrailService;
+ @Autowired
+ private BioAssayService bioAssayService;
+ @Autowired
+ private BioMaterialService bioMaterialService;
+ @Autowired
+ private ExpressionExperimentService expressionExperimentService;
+ @Autowired
+ private ExternalDatabaseService externalDatabaseService;
+ @Autowired
+ private Persister persisterHelper;
+ @Autowired
private PreprocessorService preprocessorService;
+ @Autowired
private QuantitationTypeService quantitationTypeService;
+ @Autowired
+ private MessageUtil messageUtil;
- @SuppressWarnings("deprecation")
- public ExpressionExperimentFormController() {
- /*
- * if true, reuses the same command object across the edit-submit-process (get-post-process).
- */
- this.setSessionForm( true );
- this.setCommandClass( ExpressionExperimentEditValueObject.class );
+ /**
+ * Set up a custom property editor for converting form inputs to real objects. Override this to add additional
+ * custom editors (call super.initBinder() in your implementation)
+ */
+ @InitBinder
+ protected void initBinder( WebDataBinder binder ) {
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ binder.registerCustomEditor( Integer.class, null, new CustomNumberEditor( Integer.class, nf, true ) );
+ binder.registerCustomEditor( Long.class, null, new CustomNumberEditor( Long.class, nf, true ) );
+ binder.registerCustomEditor( byte[].class, new ByteArrayMultipartFileEditor() );
}
- @Override
- public ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response, Object command,
- BindException errors ) throws Exception {
-
- BaseFormController.log.debug( "entering processFormSubmission" );
+ @RequestMapping(value = "/expressionExperiment/editExpressionExperiment.html", method = RequestMethod.GET)
+ public ModelAndView getExpressionExperiment( @RequestParam("id") Long id, HttpServletRequest request ) {
+ ExpressionExperimentValueObject command = this.formBackingObject( id, request );
- Long id = ( ( ExpressionExperimentValueObject ) command ).getId();
+ ExpressionExperimentEditController.log.debug( "entering processFormSubmission" );
if ( request.getParameter( "cancel" ) != null ) {
if ( id != null ) {
- return new ModelAndView( new RedirectView(
- "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath()
- + "/expressionExperiment/showExpressionExperiment.html?id=" + id ) );
+ return new ModelAndView( new RedirectView( "/expressionExperiment/showExpressionExperiment.html?id=" + id, true ) );
}
- BaseFormController.log.warn( "Cannot find details view due to null id. Redirecting to overview" );
- return new ModelAndView( new RedirectView(
- "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath()
- + "/expressionExperiment/showAllExpressionExperiments.html" ) );
+ ExpressionExperimentEditController.log.warn( "Cannot find details view due to null id. Redirecting to overview" );
+ return new ModelAndView( new RedirectView( "/expressionExperiment/showAllExpressionExperiments.html", true ) );
}
- ModelAndView mav = super.processFormSubmission( request, response, command, errors );
+ ModelAndView mav = new ModelAndView( "expressionExperiment.edit" );
ExpressionExperiment ee = expressionExperimentService.loadOrFail( id );
@@ -111,101 +134,7 @@ public ModelAndView processFormSubmission( HttpServletRequest request, HttpServl
// add count of designElementDataVectors
mav.addObject( "designElementDataVectorCount",
expressionExperimentService.getDesignElementDataVectorCount( ee ) );
- return mav;
- }
-
- /**
- * @param auditTrailService the auditTrailService to set
- */
- public void setAuditTrailService( AuditTrailService auditTrailService ) {
- this.auditTrailService = auditTrailService;
- }
-
- /**
- * @param bioAssayService the bioAssayService to set
- */
- public void setBioAssayService( BioAssayService bioAssayService ) {
- this.bioAssayService = bioAssayService;
- }
-
- /**
- * @param bioMaterialService the bioMaterialService to set
- */
- public void setBioMaterialService( BioMaterialService bioMaterialService ) {
- this.bioMaterialService = bioMaterialService;
- }
-
- public void setExpressionExperimentService( ExpressionExperimentService expressionExperimentService ) {
- this.expressionExperimentService = expressionExperimentService;
- }
-
- public void setExternalDatabaseService( ExternalDatabaseService externalDatabaseService ) {
- this.externalDatabaseService = externalDatabaseService;
- }
-
- /**
- * @param persisterHelper the persisterHelper to set
- */
- public void setPersisterHelper( Persister persisterHelper ) {
- this.persisterHelper = persisterHelper;
- }
-
- public void setPreprocessorService( PreprocessorService preprocessorService ) {
- this.preprocessorService = preprocessorService;
- }
-
- public void setQuantitationTypeService( QuantitationTypeService quantitationTypeService ) {
- this.quantitationTypeService = quantitationTypeService;
- }
-
- @Override
- protected Object formBackingObject( HttpServletRequest request ) {
- if ( !SecurityUtil.isUserLoggedIn() ) {
- throw new AccessDeniedException( "User does not have access to experiment management" );
- }
-
- Long id;
- try {
- id = Long.parseLong( request.getParameter( "id" ) );
- } catch ( NumberFormatException e ) {
- this.saveMessage( request, "Id was not a number " + request.getParameter( "id" ) );
- throw new IllegalArgumentException( "Id was not a number " + request.getParameter( "id" ) );
- }
-
- BaseFormController.log.debug( id );
- ExpressionExperimentEditValueObject obj;
-
- ExpressionExperiment ee = expressionExperimentService.loadAndThawLiteOrFail( id,
- EntityNotFoundException::new, String.format( "No experiment with ID %d", id ) );
- List qts = new ArrayList<>(
- quantitationTypeService.loadValueObjects( expressionExperimentService.getQuantitationTypes( ee ) ) );
-
- ExpressionExperimentValueObject vo = expressionExperimentService.loadValueObject( ee );
-
- if ( vo == null ) {
- throw new EntityNotFoundException( String.format( "Could load experiment VO with ID %d", id ) );
- }
-
- obj = new ExpressionExperimentEditValueObject( vo );
-
- obj.setQuantitationTypes( qts );
- obj.setBioAssays( BioAssayValueObject.convert2ValueObjects( ee.getBioAssays() ) );
-
- this.saveMessage( request, "Editing dataset" );
-
- return obj;
- }
-
- private static final List
- STANDARD_QUANTITATION_TYPES = Arrays.stream( StandardQuantitationType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
- SCALE_TYPES = Arrays.stream( ScaleType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
- GENERAL_QUANTITATION_TYPES = Arrays.stream( GeneralType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ),
- REPRESENTATIONS = Arrays.stream( PrimitiveType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() );
-
- @Override
- protected Map