All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.broadleafcommerce.openadmin.web.controller.AdminAbstractController Maven / Gradle / Ivy

There is a newer version: 3.1.15-GA
Show newest version
/*
 * Copyright 2008-2009 the original author or authors.
 *
 * 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 org.broadleafcommerce.openadmin.web.controller;

import org.apache.commons.collections.CollectionUtils;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.persistence.EntityConfiguration;
import org.broadleafcommerce.common.util.BLCMapUtils;
import org.broadleafcommerce.common.util.TypedClosure;
import org.broadleafcommerce.common.web.controller.BroadleafAbstractController;
import org.broadleafcommerce.openadmin.dto.BasicFieldMetadata;
import org.broadleafcommerce.openadmin.dto.ClassMetadata;
import org.broadleafcommerce.openadmin.dto.ClassTree;
import org.broadleafcommerce.openadmin.dto.DynamicResultSet;
import org.broadleafcommerce.openadmin.dto.Entity;
import org.broadleafcommerce.openadmin.dto.FieldMetadata;
import org.broadleafcommerce.openadmin.dto.FilterAndSortCriteria;
import org.broadleafcommerce.openadmin.dto.Property;
import org.broadleafcommerce.openadmin.dto.SortDirection;
import org.broadleafcommerce.openadmin.server.domain.PersistencePackageRequest;
import org.broadleafcommerce.openadmin.server.security.domain.AdminSection;
import org.broadleafcommerce.openadmin.server.security.remote.SecurityVerifier;
import org.broadleafcommerce.openadmin.server.security.service.navigation.AdminNavigationService;
import org.broadleafcommerce.openadmin.server.service.AdminEntityService;
import org.broadleafcommerce.openadmin.web.form.component.ListGrid;
import org.broadleafcommerce.openadmin.web.form.entity.DynamicEntityFormInfo;
import org.broadleafcommerce.openadmin.web.form.entity.EntityForm;
import org.broadleafcommerce.openadmin.web.form.entity.EntityFormValidator;
import org.broadleafcommerce.openadmin.web.form.entity.Field;
import org.broadleafcommerce.openadmin.web.form.entity.FieldGroup;
import org.broadleafcommerce.openadmin.web.form.entity.Tab;
import org.broadleafcommerce.openadmin.web.handler.AdminNavigationHandlerMapping;
import org.broadleafcommerce.openadmin.web.service.FormBuilderService;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * An abstract controller that provides convenience methods and resource declarations for the Admin
 *
 * Operations that are shared between all admin controllers belong here.
 *
 * @see org.broadleafcommerce.openadmin.web.handler.AdminNavigationHandlerMapping
 * @author elbertbautista
 * @author apazzolini
 */
public abstract class AdminAbstractController extends BroadleafAbstractController {
    
    public static final String FILTER_VALUE_SEPARATOR = "|";
    public static final String FILTER_VALUE_SEPARATOR_REGEX = "\\|";

    // ***********************
    // RESOURCE DECLARATIONS *
    // ***********************

    @Resource(name = "blAdminEntityService")
    protected AdminEntityService service;

    @Resource(name = "blFormBuilderService")
    protected FormBuilderService formService;
    
    @Resource(name = "blAdminNavigationService")
    protected AdminNavigationService adminNavigationService;
    
    @Resource(name = "blEntityConfiguration")
    protected EntityConfiguration entityConfiguration;

    @Resource(name = "blEntityFormValidator")
    protected EntityFormValidator entityFormValidator;
    
    @Resource(name="blAdminSecurityRemoteService")
    protected SecurityVerifier adminRemoteSecurityService;
    
    @Resource(name = "blMainEntityActionsExtensionManager")
    protected MainEntityActionsExtensionManager mainEntityActionsExtensionManager;
    
    // *********************************************************
    // UNBOUND CONTROLLER METHODS (USED BY DIFFERENT SECTIONS) *
    // *********************************************************
    
    /**
     * Returns a partial representing a dynamic form. An example of this is the dynamic fields that render
     * on structured content, which are determined by the currently selected structured content type. This 
     * method is typically only invoked through Javascript and used to replace the current dynamic form with
     * the one for the newly selected type.
     * 
     * @param request
     * @param response
     * @param model
     * @param pathVars
     * @param info
     * @return the return view path
     * @throws Exception
     */
    protected String getDynamicForm(HttpServletRequest request, HttpServletResponse response, Model model,
            Map pathVars,
            DynamicEntityFormInfo info) throws Exception {
        String sectionKey = getSectionKey(pathVars);
        EntityForm blankFormContainer = new EntityForm();
        EntityForm dynamicForm = getBlankDynamicFieldTemplateForm(info);

        blankFormContainer.putDynamicForm(info.getPropertyName(), dynamicForm);
        model.addAttribute("entityForm", blankFormContainer);
        model.addAttribute("dynamicPropertyName", info.getPropertyName());
        
        String reqUrl = request.getRequestURL().toString();
        reqUrl = reqUrl.substring(0, reqUrl.indexOf("/dynamicForm"));
        model.addAttribute("currentUrl", reqUrl);
        
        setModelAttributes(model, sectionKey);
        return "views/dynamicFormPartial";
    }
    
    // **********************************
    // HELPER METHODS FOR BUILDING DTOS *
    // **********************************

    /**
     * Convenience method for obtaining a ListGrid DTO object for a collection. Note that if no criteria is
     * available, then this should be null (or empty)
     * 
     * @param mainMetadata class metadata for the root entity that this collectionProperty relates to
     * @param id foreign key from the root entity for collectionProperty
     * @param collectionProperty property that this collection should be based on from the root entity
     * @param form the criteria form model attribute
     * @param sectionKey the current main section key
     * @return the list grid
     * @throws ServiceException
     */
    protected ListGrid getCollectionListGrid(ClassMetadata mainMetadata, Entity entity, Property collectionProperty,
            MultiValueMap requestParams, String sectionKey)
            throws ServiceException {
        DynamicResultSet drs = service.getRecordsForCollection(mainMetadata, entity, collectionProperty,
                getCriteria(requestParams), getStartIndex(requestParams), getMaxIndex(requestParams));

        String idProperty = service.getIdProperty(mainMetadata);
        ListGrid listGrid = formService.buildCollectionListGrid(entity.findProperty(idProperty).getValue(), drs, 
                collectionProperty, sectionKey);
        listGrid.setListGridType(ListGrid.Type.INLINE);

        return listGrid;
    }

    /**
     * @see #getBlankDynamicFieldTemplateForm(DynamicEntityFormInfo, EntityForm)
     * @param info
     * @throws ServiceException
     */
    protected EntityForm getBlankDynamicFieldTemplateForm(DynamicEntityFormInfo info) throws ServiceException {
        return getBlankDynamicFieldTemplateForm(info, null);
    }

    /**
     * Convenience method for obtaining a blank dynamic field template form. For example, if the main entity form should
     * render different fields depending on the value of a specific field in that main form itself, the "dynamic" fields
     * are generated by this method. Because this is invoked when a new value is chosen, the form generated by this method
     * will never have values set.
     * 
     * @param info
     * @return the entity form
     * @throws ServiceException
     */
    protected EntityForm getBlankDynamicFieldTemplateForm(DynamicEntityFormInfo info, EntityForm dynamicFormOverride) 
            throws ServiceException {
        // We need to inspect with the second custom criteria set to the id of
        // the desired structured content type
        PersistencePackageRequest ppr = PersistencePackageRequest.standard()
                .withCeilingEntityClassname(info.getCeilingClassName())
                .withCustomCriteria(new String[] { info.getCriteriaName(), null, info.getPropertyName(), info.getPropertyValue() });

        ClassMetadata cmd = service.getClassMetadata(ppr);
        
        EntityForm dynamicForm = formService.createEntityForm(cmd);
        dynamicForm.clearFieldsMap();

        if (dynamicFormOverride != null) {
            dynamicFormOverride.clearFieldsMap();
            Map fieldOverrides = dynamicFormOverride.getFields();
            for (Entry override : fieldOverrides.entrySet()) {
                if (dynamicForm.getFields().containsKey(override.getKey())) {
                    dynamicForm.getFields().get(override.getKey()).setValue(override.getValue().getValue());
                }
            }
        }
        
        // Set the specialized name for these fields - we need to handle them separately
        dynamicForm.clearFieldsMap();
        for (Tab tab : dynamicForm.getTabs()) {
            for (FieldGroup group : tab.getFieldGroups()) {
                for (Field field : group.getFields()) {
                    field.setName(info.getPropertyName() + DynamicEntityFormInfo.FIELD_SEPARATOR + field.getName());
                }
            }
        }

        return dynamicForm;
    }
    
    /**
     * Convenience method for obtaining a dynamic field template form for a particular entity. This method differs from
     * {@link #getBlankDynamicFieldTemplateForm(DynamicEntityFormInfo)} in that it will fill out the current values for 
     * the fields in this dynamic form from the database. This method is invoked when the initial view of a page containing
     * a dynamic form is triggered.
     * 
     * Optionally, you can pass in a pre-existing dynamic form to this method that already has updated values. Example usage
     * would be for after validation has failed and you do not want to lookup old values from the database again.
     * 
     * @param info
     * @param entityId
     * @param dynamicForm optional dynamic form that already has values to fill out
     * @return the entity form
     * @throws ServiceException
     */
    protected EntityForm getDynamicFieldTemplateForm(DynamicEntityFormInfo info, String entityId, EntityForm dynamicFormOverride) 
            throws ServiceException {
        PersistencePackageRequest ppr = PersistencePackageRequest.standard()
                .withCeilingEntityClassname(info.getCeilingClassName())
                .withCustomCriteria(new String[] { info.getCriteriaName(), entityId, info.getPropertyName(), info.getPropertyValue() });
        ClassMetadata cmd = service.getClassMetadata(ppr);
        Entity entity = service.getRecord(ppr, entityId, cmd, true);        
        
        List fieldsToMove = new ArrayList();
        // override the results of the entity with the dynamic form passed in
        if (dynamicFormOverride != null) {
            dynamicFormOverride.clearFieldsMap();
            Map fieldOverrides = dynamicFormOverride.getFields();
            for (Entry override : fieldOverrides.entrySet()) {
                if (entity.getPMap().containsKey(override.getKey())) {
                    entity.getPMap().get(override.getKey()).setValue(override.getValue().getValue());
                } else {
                    fieldsToMove.add(override.getValue());
                }
            }
        }
        
        // Assemble the dynamic form for structured content type
        EntityForm dynamicForm = formService.createEntityForm(cmd, entity);
        
        for (Field field : fieldsToMove) {
            FieldMetadata fmd = cmd.getPMap().get(field.getName()).getMetadata();
            if (fmd instanceof BasicFieldMetadata) {
                BasicFieldMetadata bfmd = (BasicFieldMetadata) fmd;
                field.setFieldType(bfmd.getFieldType().toString());
                field.setFriendlyName(bfmd.getFriendlyName());
                field.setRequired(bfmd.getRequired());
            }
            dynamicForm.addField(field);
        }
        
        // Set the specialized name for these fields - we need to handle them separately
        dynamicForm.clearFieldsMap();
        for (Tab tab : dynamicForm.getTabs()) {
            for (FieldGroup group : tab.getFieldGroups()) {
                for (Field field : group.getFields()) {
                    field.setName(info.getPropertyName() + DynamicEntityFormInfo.FIELD_SEPARATOR + field.getName());
                }
            }
        }
    
        return dynamicForm;
    }

    /**
     * This method will scan the entityForm for all dynamic form fields and pull them out
     * as appropriate.
     * 
     * @param entityForm
     */
    protected void extractDynamicFormFields(EntityForm entityForm) {
        Map dynamicFields = new HashMap();
        
        // Find all of the dynamic form fields
        for (Entry entry : entityForm.getFields().entrySet()) {
            if (entry.getKey().contains(DynamicEntityFormInfo.FIELD_SEPARATOR)) { 
                dynamicFields.put(entry.getKey(), entry.getValue());
            }
        }
        
        // Remove the dynamic form fields from the main entity - they are persisted separately
        for (Entry entry : dynamicFields.entrySet()) {
            entityForm.removeField(entry.getKey());
        }
        
        // Create the entity form for the dynamic form, as it needs to be persisted separately
        for (Entry entry : dynamicFields.entrySet()) {
            String[] fieldName = entry.getKey().split("\\" + DynamicEntityFormInfo.FIELD_SEPARATOR);
            DynamicEntityFormInfo info = entityForm.getDynamicFormInfo(fieldName[0]);
                    
            EntityForm dynamicForm = entityForm.getDynamicForm(fieldName[0]);
            if (dynamicForm == null) {
                dynamicForm = new EntityForm();
                dynamicForm.setCeilingEntityClassname(info.getCeilingClassName());
                entityForm.putDynamicForm(fieldName[0], dynamicForm);
            }
            
            entry.getValue().setName(fieldName[1]);
            dynamicForm.addField(entry.getValue());
        }
    }

    
    // ***********************************************
    // HELPER METHODS FOR SECTION-SPECIFIC OVERRIDES *
    // ***********************************************
    
    /**
     * This method is used to determine the current section key. For this default implementation, the sectionKey is pulled
     * from the pathVariable, {sectionKey}, as defined by the request mapping on this controller. To support controller
     * inheritance and allow more specialized controllers to delegate some methods to this basic controller, overridden
     * implementations of this method could return a hardcoded value instead of reading the map
     * 
     * @param pathVars - the map of all currently bound path variables for this request
     * @return the sectionKey for this request
     */
    protected String getSectionKey(Map pathVars) {
        return pathVars.get("sectionKey");
    }
    
    /**
     * 

Helper method to return an array of {@link org.broadleafcommerce.openadmin.dto.FilterAndSortCriteria} based on a map of propertyName -> list of criteria * value. This will also grab the sorts off of the request parameters, if any.

* *

The multi-valued map allows users to specify multiple criteria values per property, as well as multiple sort * properties and sort directions. For multiple sort properties and sort directions, these would usually come in as * request parameters like: *
*
* ....?sortProperty=defaultSku.name&sortProperty=manufacturer&sortDirection=ASCENDING&sortDirection=DESCENDING *
*
* This would attach criteria such that defaultSku.name was sorted ascending, and manufacturer was sorted descending

* * @param requestParams usually a {@link MultiValueMap} that has been bound by a controller to receive all of the * request parameters that are not explicitly named * @return the final array of {@link org.broadleafcommerce.openadmin.dto.FilterAndSortCriteria} to pass to the fetch * * @see {@link #getSortPropertyNames(Map)} * @see {@link #getSortDirections(Map)} */ protected FilterAndSortCriteria[] getCriteria(Map> requestParams) { if (requestParams == null || requestParams.isEmpty()) { return null; } List result = new ArrayList(); for (Entry> entry : requestParams.entrySet()) { if (!entry.getKey().equals(FilterAndSortCriteria.SORT_PROPERTY_PARAMETER) && !entry.getKey().equals(FilterAndSortCriteria.SORT_DIRECTION_PARAMETER)) { List values = entry.getValue(); List collapsedValues = new ArrayList(); for (String value : values) { if (value.contains(FILTER_VALUE_SEPARATOR)) { String[] vs = value.split(FILTER_VALUE_SEPARATOR_REGEX); for (String v : vs) { collapsedValues.add(v); } } else { collapsedValues.add(value); } } FilterAndSortCriteria fasCriteria = new FilterAndSortCriteria(entry.getKey(), collapsedValues); result.add(fasCriteria); } } List sortProperties = getSortPropertyNames(requestParams); List sortDirections = getSortDirections(requestParams); if (CollectionUtils.isNotEmpty(sortProperties)) { //set up a map to determine if there is already some criteria set for the sort property Map fasMap = BLCMapUtils.keyedMap(result, new TypedClosure() { @Override public String getKey(FilterAndSortCriteria value) { return value.getPropertyId(); } }); for (int i = 0; i < sortProperties.size(); i++) { boolean sortAscending = SortDirection.ASCENDING.toString().equals(sortDirections.get(i)); FilterAndSortCriteria propertyCriteria = fasMap.get(sortProperties.get(i)); //If there is already criteria for this property, attach the sort to that. Otherwise, create some new //FilterAndSortCriteria for the sort if (propertyCriteria != null) { propertyCriteria.setSortAscending(sortAscending); } else { FilterAndSortCriteria fasc = new FilterAndSortCriteria(sortProperties.get(i)); fasc.setSortAscending(sortAscending); result.add(fasc); } } } return result.toArray(new FilterAndSortCriteria[result.size()]); } /** * Obtains the list of sort directions from the bound request parameters. Note that these should appear in the same * relative order as {@link #getSortPropertyNames(Map)} * * @param requestParams * @return */ protected List getSortDirections(Map> requestParams) { List sortTypes = requestParams.get(FilterAndSortCriteria.SORT_DIRECTION_PARAMETER); return sortTypes; } /** * Obtains the list of property names to sort on from the bound request parameters. Note that these should appear in the * same relative order as {@link #getSortDirections(Map)}. * * @param requestParams * @return */ protected List getSortPropertyNames(Map> requestParams) { return requestParams.get(FilterAndSortCriteria.SORT_PROPERTY_PARAMETER); } /** * Gets the fully qualified ceiling entity classname for this section. If this section is not explicitly defined in * the database, will return the value passed into this function. For example, if there is a mapping from "/myentity" to * "com.mycompany.myentity", both "http://localhost/myentity" and "http://localhost/com.mycompany.myentity" are valid * request paths. * * @param sectionKey * @return the className for this sectionKey if found in the database or the sectionKey if not */ protected String getClassNameForSection(String sectionKey) { AdminSection section = adminNavigationService.findAdminSectionByURI("/" + sectionKey); return (section == null) ? sectionKey : section.getCeilingEntity(); } /** * If there are certain types of entities that should not be allowed to be created, an override of this method would be * able to specify that. It could also add additional types if desired. * * @param classTree * @return a List representing all potentially avaialble entity types to create */ protected List getAddEntityTypes(ClassTree classTree) { return classTree.getCollapsedClassTrees(); } /** * This method is called when attempting to add new entities that have a polymorphic tree. * * If this method returns null, there is no default type set for this particular entity type, and the user will be * presented with a selection of possible types to utilize. * * If it returns a non-null value, the returned fullyQualifiedClassname will be used and will bypass the selection step. * * @return null if there is no default type, otherwise the default type */ protected String getDefaultEntityType() { return null; } /** * This method is invoked for every request for this controller. By default, we do not want to specify a custom * criteria, but specialized controllers may want to. * * @return the custom criteria for this section for all requests, if any */ protected String[] getSectionCustomCriteria() { return null; } /** * A hook method that is invoked every time the {@link #getSectionPersistencePackageRequest(String)} method is invoked. * This allows specialized controllers to hook into every request and manipulate the persistence package request as * desired. * * @param ppr */ protected void attachSectionSpecificInfo(PersistencePackageRequest ppr) { } /** * Obtains the requested start index parameter * * @param requestParams * @return */ protected Integer getStartIndex(Map> requestParams) { if (requestParams == null || requestParams.isEmpty()) { return null; } List startIndex = requestParams.get(FilterAndSortCriteria.START_INDEX_PARAMETER); return CollectionUtils.isEmpty(startIndex) ? null : Integer.parseInt(startIndex.get(0)); } /** * Obtains the requested max index parameter * * @param requestParams * @return */ protected Integer getMaxIndex(Map> requestParams) { if (requestParams == null || requestParams.isEmpty()) { return null; } List maxIndex = requestParams.get(FilterAndSortCriteria.MAX_INDEX_PARAMETER); return CollectionUtils.isEmpty(maxIndex) ? null : Integer.parseInt(maxIndex.get(0)); } // ************************ // GENERIC HELPER METHODS * // ************************ /** * Attributes to add to the model on every request * * @param model * @param sectionKey */ protected void setModelAttributes(Model model, String sectionKey) { AdminSection section = adminNavigationService.findAdminSectionByURI("/" + sectionKey); if (section != null) { model.addAttribute("sectionKey", sectionKey); model.addAttribute(AdminNavigationHandlerMapping.CURRENT_ADMIN_MODULE_ATTRIBUTE_NAME, section.getModule()); model.addAttribute(AdminNavigationHandlerMapping.CURRENT_ADMIN_SECTION_ATTRIBUTE_NAME, section); } } /** * Returns a PersistencePackageRequest for the given sectionClassName. Will also invoke the * {@link #getSectionCustomCriteria()} and {@link #attachSectionSpecificInfo(PersistencePackageRequest)} to allow * specialized controllers to manipulate the request for every action in this controller. * * @param sectionClassName * @return the PersistencePacakageRequest */ protected PersistencePackageRequest getSectionPersistencePackageRequest(String sectionClassName) { PersistencePackageRequest ppr = PersistencePackageRequest.standard() .withCeilingEntityClassname(sectionClassName) .withCustomCriteria(getSectionCustomCriteria()); attachSectionSpecificInfo(ppr); return ppr; } /** * Returns the result of a call to {@link #getSectionPersistencePackageRequest(String)} with the additional filter * and sort criteria attached. * * @param sectionClassName * @param filterAndSortCriteria * @return the PersistencePacakageRequest */ protected PersistencePackageRequest getSectionPersistencePackageRequest(String sectionClassName, MultiValueMap requestParams) { FilterAndSortCriteria[] fascs = getCriteria(requestParams); return getSectionPersistencePackageRequest(sectionClassName) .withFilterAndSortCriteria(fascs) .withStartIndex(getStartIndex(requestParams)) .withMaxIndex(getMaxIndex(requestParams)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy