Как интегрировать соглашения Struts с плитками, чтобы сохранить преимущества соглашений

Как интегрировать соглашения Struts с плитками, сохраняя при этом преимущества соглашений?

Проблема в том, что соглашения автоматически связывают URL-адрес с действием-результатом и делают это хорошо для результатов JSP, Velocity и FreeMarker. Он не рассчитывает иметь дело с результатом плитки.

При использовании плиток мы обычно хотим, чтобы все наши действия пользовательского интерфейса (в отличие от действий службы json/xml) использовали плитки, но при этом мы теряем соглашение для компонента результата и должны использовать аннотации. Аннотации позволяют нам отклоняться от ожидаемого, но в большом приложении, когда предполагается использование тайлов, это раздражает. Дальнейшие соглашения позволяют нам создавать действия, указав только представление. Мы хотели бы сохранить это преимущество и при использовании тайлов. Чтобы исправить это, нам нужно установить соглашение, которое распространяется на результат плитки, чтобы нам не нужно было использовать аннотации для привязки действия к результату плитки, и чтобы мы могли продолжать создавать JSP без классов действий, которые получат преимущества об условностях (без xml) и преимуществах плитки (вся плита котла учтена в плитках).

Как этого добиться?

Это самостоятельный ответ, чтобы помочь другим, которые хотят решить эту проблему


person Quaternion    schedule 20.04.2013    source источник


Ответы (1)


Вот необходимые шаги:

  • Результат создания пользовательских плиток, который динамически создает строку «местоположение» (строка местоположения — это значение, передаваемое плиткам), которое учитывает пространство имен, actionName.
  • Создайте пакет, который использует этот результат (с именем «плитки»), и используйте соглашения в качестве родительского пакета.
  • Внедрите и зарегистрируйте «com.opensymphony.xwork2.UnknownHandler», этот шаг является наиболее важным, поскольку этот обработчик вызывается, когда результат не может быть разрешен.
  • Определения плиток, в которых используется «местоположение», переданное с первого шага.

Вышеуказанные шаги требуют следующего в struts.xml

<struts>
   <constant name="struts.convention.default.parent.package" value="tiles-package"/>
   <bean type="com.opensymphony.xwork2.UnknownHandler" name="tilesUnknownHandler" class="com.kenmcwilliams.tiles.result.TilesUnknownHandler"/>

   <package  name="tiles-package" extends="convention-default">
      <result-types>
         <result-type default="true" name="tiles" class="com.kenmcwilliams.tiles.result.TilesResult"/>
      </result-types>
   </package>   
</struts>

Реализация пользовательского типа результата:

package com.kenmcwilliams.tiles.result;

import com.opensymphony.xwork2.ActionInvocation;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.ServletDispatcherResult;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.request.ApplicationContext;
import org.apache.tiles.request.servlet.ServletRequest;
import org.apache.tiles.request.servlet.ServletUtil;

public class TilesResult extends ServletDispatcherResult {

    private static final Logger log = Logger.getLogger(TilesResult.class.getName());

    public TilesResult() {
        super();
    }

    public TilesResult(String location) {
        super(location);
    }

    @Override
    public void doExecute(String location, ActionInvocation invocation) throws Exception {
        //location = "test.definition"; //for test
        log.log(Level.INFO, "TilesResult doExecute() location: {0}", location);
        //Start simple conventions
        //
        if (/** tiles && **/location == null) {
            String namespace = invocation.getProxy().getNamespace();
            String actionName = invocation.getProxy().getActionName();
            location = namespace + "#" + actionName + ".jsp"; //Warning forcing extension
            log.log(Level.INFO, "TilesResult namespace: {0}", namespace);
            log.log(Level.INFO, "TilesResult actionName: {0}", actionName);
            log.log(Level.INFO, "TilesResult location: {0}", location);
        }
        //End simple conventions
        setLocation(location);
        ServletContext context = ServletActionContext.getServletContext();
        ApplicationContext applicationContext = ServletUtil.getApplicationContext(context);
        TilesContainer container = TilesAccess.getContainer(applicationContext);
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        ServletRequest servletRequest = new ServletRequest(applicationContext, request, response);
        container.render(location, servletRequest);
    }
}

Реализация TilesUnknownHandler:

package com.kenmcwilliams.tiles.result;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig.Builder;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import flexjson.JSONSerializer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.convention.ConventionUnknownHandler;

public class TilesUnknownHandler extends ConventionUnknownHandler {

    private static final Logger log = Logger.getLogger(TilesUnknownHandler.class.getName());
    private static final String conventionBase = "/WEB-INF/content";

    @Inject
    public TilesUnknownHandler(Configuration configuration, ObjectFactory objectFactory,
            ServletContext servletContext, Container container,
            @Inject("struts.convention.default.parent.package") String defaultParentPackageName,
            @Inject("struts.convention.redirect.to.slash") String redirectToSlash,
            @Inject("struts.convention.action.name.separator") String nameSeparator) {
        super(configuration, objectFactory, servletContext, container, defaultParentPackageName,
                redirectToSlash, nameSeparator);
        log.info("Constructed TilesUnknownHandler");
    }

    @Override
    public ActionConfig handleUnknownAction(String namespace, String actionName)
            throws XWorkException {
        ActionConfig actionConfig;
        log.info("TilesUnknownHandler: before handleUnknownAction");
        ActionConfig handleUnknownAction = super.handleUnknownAction(namespace, actionName);

        log.info("TilesUnknownHandler: after handleUnknownAction, returning with:");
        log.log(Level.INFO, "...ActionConfig value: {0}", (new JSONSerializer().serialize(handleUnknownAction)));
        log.log(Level.INFO, "Modifying handleUnknowAction result handler");

        Map<String, ResultConfig> results = handleUnknownAction.getResults();
        ResultConfig resultConfig = results.get("success");
        Builder builder = new ResultConfig.Builder("com.opensymphony.xwork2.config.entities.ResultConfig", "com.kenmcwilliams.tiles.result.TilesResult");
        Map<String, String> params = resultConfig.getParams();

        String tilesResultString = null;
        String location = params.get("location");
        if (location != null && !location.isEmpty()) {
            int length = conventionBase.length();

            if(StringUtils.startsWith(location, conventionBase)){
                String subString = location.substring(length); //chop off "/WEB-INF/content"
                int count = StringUtils.countMatches(subString, "/");//TODO: maybe check for "//", although I don't know why it would be in the string
                if (count == 1){//empty namespace
                    tilesResultString = subString.replaceFirst("/", "#"); //TODO: because I am doing a straight replacement of the last element the else can probably be removed
                }else{ //replace the last slash between the namespace and the file with "#"
                    int lastIndex = subString.lastIndexOf("/");
                    //subString.substring(lastIndex, lastIndex);
                    String nameSpace = subString.substring(0, lastIndex);
                    String file = subString.substring(lastIndex + 1);
                    tilesResultString = nameSpace + "#" + file;
                }
            }
        }

        Map<String, String> myParams = new LinkedHashMap<String, String>();
        myParams.put("location", tilesResultString);

        builder.addParams(myParams);
        ResultConfig build = builder.build();
        Map<String, ResultConfig> myMap = new LinkedHashMap<String, ResultConfig>();
        myMap.put("success", build);
        log.log(Level.INFO, "\n\n...results: {0}\n\n", (new JSONSerializer().serialize(results)));
        actionConfig = new ActionConfig.Builder(handleUnknownAction).addResultConfigs(myMap).build();
        //className("com.kenmcwilliams.tiles.result.TilesResult")
        return actionConfig;
    }

    @Override
    public Result handleUnknownResult(ActionContext actionContext, String actionName,
            ActionConfig actionConfig, String resultCode) throws XWorkException {
        log.info("TilesUnknownHandler: before handleUnknownResult");
        Result handleUnknownResult = super.handleUnknownResult(actionContext, actionName, actionConfig, resultCode);
        log.info("TilesUnknownHandler: after handleUnknownResult, returning with:");
        log.log(Level.INFO, "...Result value: {0}", (new JSONSerializer().serialize(handleUnknownResult)));
        return handleUnknownResult;
    }
}

Пример того, как использовать нашу строку «местоположение», которая имеет форму: NameSpace + «#» + ActionName + «.jsp», обратите внимание на это определение <definition name="REGEXP:(.*)#(.*)" extends="default"> в следующем:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
    <definition name="default" template="/WEB-INF/template/template.jsp">
        <put-list-attribute name="cssList" cascade="true">
            <add-attribute value="/style/cssreset-min.css" />
            <add-attribute value="/style/cssfonts-min.css" />
            <add-attribute value="/style/cssbase-min.css" />  
            <add-attribute value="/style/grids-min.css" />
            <add-attribute value="/script/jquery-ui-1.8.24.custom/css/ui-lightness/jquery-ui-1.8.24.custom.css" />
            <add-attribute value="/style/style.css" />
        </put-list-attribute>    
        <put-list-attribute name="jsList" cascade="true">
            <add-attribute value="/script/jquery/1.8.1/jquery.min.js" />
            <add-attribute value="/script/jquery-ui-1.8.24.custom/js/jquery-ui-1.8.24.custom.min.js" />
            <add-attribute value="/script/jquery.sort.js" />
            <add-attribute value="/script/custom/jquery-serialize.js" />
        </put-list-attribute>   
        <put-attribute name="title" value="defaults-name" cascade="true"  type="string"/>
        <put-attribute name="head" value="/WEB-INF/template/head.jsp"/>
        <put-attribute name="header" value="/WEB-INF/template/header.jsp"/>
        <put-attribute name="body" value="/WEB-INF/template/body.jsp"/>
        <put-attribute name="footer" value="/WEB-INF/template/footer.jsp"/>
    </definition>

    <definition name="REGEXP:(.*)#(.*)"  extends="default">
        <put-attribute name="title" cascade="true" expression="OGNL:@com.opensymphony.xwork2.ActionContext@getContext().name"/>
        <put-attribute name="body" value="/WEB-INF/content{1}/{2}"/>
    </definition>

</tiles-definitions>

С этим вы можете создавать JSP в /WEB-INF/content/someplace/my-action.jsp.

Точно так же, как и в случае с соглашениями, И плитки также украсят его соответствующим образом, если вы создадите класс действия с именем com.myapp.action.someplace.MyAction без какого-либо типа результата, этот код будет выполняться, и результат /WEB-INF/content/someplace/my-action.jsp все равно будет отображаться.

Там у вас есть условности + плитки без аннотаций (ну для нормального случая).

ПРИМЕЧАНИЯ:

  • Этот ответ, конечно, не идеален, но он представляет собой рабочий пример стратегии, которую можно применить к другим технологиям просмотра (сетка сайта и другие).
  • В настоящее время вы можете видеть, что «.jsp» добавляется к результату плитки, а НЕ к определениям плитки, это негибко. Конкретное расширение должно быть указано внутри плиток, то есть атрибут body в определении должен добавлять конкретный тип представления (.jsp, .fml, .vm), потому что в то время вы должны знать лучше.
  • Важно отметить, что определения пробуются в том порядке, в котором они даны, поэтому вы можете переопределить обычный случай REGEXP:(.*)#(.*), поместив определения между определениями default и REGEXP:(.*)#(.*). Например, определение под названием authenticated\(.*) может быть помещено между этими двумя определениями. В конце концов, если бы вы не могли этого сделать и все страницы должны были быть выложены одинаковыми плитками, мы бы действительно не использовали плитки!
  • Просто чтобы вы знали, что при использовании плитки 3 (плагин struts2tiles3) вы можете использовать все три типа технологий просмотра (jsp, freemarker, speed) для создания одной плитки. Оно работает. Вероятно, вы будете постоянно использовать одну технологию просмотра, но приятно знать, что это возможно.
person Quaternion    schedule 20.04.2013
comment
Это представлено как предложение Struts2 JIRA? - person Lukasz Lenart; 23.07.2013
comment
Хотел включить это в плагин, но из-за конфигурации плиток (xml) в пользовательской части... это выглядит немного запутанно. Лучше знать, что происходит сверху донизу, если магия не может быть инкапсулирована. Не возражал бы, если бы из этого можно было сделать учебник для сайта struts2. - person Quaternion; 23.07.2013
comment
Вы можете зарегистрировать проблему со ссылкой на это? - person Lukasz Lenart; 24.07.2013