/*
* Copyright 2014 astamuse company,Ltd.
*
* 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 com.astamuse.asta4d.web.util.message;
import static com.astamuse.asta4d.render.SpecialRenderer.Clear;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.astamuse.asta4d.Context;
import com.astamuse.asta4d.data.ContextBindData;
import com.astamuse.asta4d.render.ElementNotFoundHandler;
import com.astamuse.asta4d.render.ElementSetter;
import com.astamuse.asta4d.render.Renderable;
import com.astamuse.asta4d.render.Renderer;
import com.astamuse.asta4d.template.ClasspathTemplateResolver;
import com.astamuse.asta4d.template.Template;
import com.astamuse.asta4d.template.TemplateException;
import com.astamuse.asta4d.template.TemplateNotFoundException;
import com.astamuse.asta4d.template.TemplateResolver;
import com.astamuse.asta4d.util.SelectorUtil;
import com.astamuse.asta4d.web.WebApplicationConfiguration;
import com.astamuse.asta4d.web.WebApplicationContext;
import com.astamuse.asta4d.web.dispatch.RedirectInterceptor;
import com.astamuse.asta4d.web.dispatch.RedirectUtil;
public class DefaultMessageRenderingHelper implements MessageRenderingHelper {
private static final Logger logger = LoggerFactory.getLogger(DefaultMessageRenderingHelper.class);
private final static String FLASH_MSG_LIST_KEY = "FLASH_MSG_LIST_KEY#" + DefaultMessageRenderingHelper.class;
protected final static class MessageHolder {
MessageRenderingSelector selector;
MessageRenderingSelector alternativeSelector;
String message;
public MessageHolder(MessageRenderingSelector selector, MessageRenderingSelector alternativeSelector, String message) {
super();
this.selector = selector;
this.alternativeSelector = alternativeSelector;
this.message = message;
}
}
public final static class MessageRenderingSelector {
private String duplicator;
private String valueTarget;
public MessageRenderingSelector() {
}
public MessageRenderingSelector(String duplicator, String valueTarget) {
super();
this.duplicator = duplicator;
this.valueTarget = valueTarget;
}
public String getDuplicator() {
return duplicator;
}
public void setDuplicator(String duplicator) {
this.duplicator = duplicator;
}
public String getValueTarget() {
return valueTarget;
}
public void setValueTarget(String valueTarget) {
this.valueTarget = valueTarget;
}
@Override
public int hashCode() {
return ((duplicator == null) ? 0 : duplicator.hashCode()) ^ ((valueTarget == null) ? 0 : valueTarget.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MessageRenderingSelector other = (MessageRenderingSelector) obj;
if (duplicator == null) {
if (other.duplicator != null)
return false;
} else if (!duplicator.equals(other.duplicator))
return false;
if (valueTarget == null) {
if (other.valueTarget != null)
return false;
} else if (!valueTarget.equals(other.valueTarget))
return false;
return true;
}
}
private TemplateResolver fallbackMessageContainerResolver = new ClasspathTemplateResolver();
private String messageGlobalContainerParentSelector = "body";
private String messageGlobalContainerSelector = "#global-msg-container";
private String messageGlobalContainerSnippetFilePath = "/com/astamuse/asta4d/web/util/message/DefaultMessageContainerSnippet.html";
private MessageRenderingSelector messageGlobalInfoSelector = new MessageRenderingSelector("#info-msg li", ":root");
private MessageRenderingSelector messageGlobalWarnSelector = new MessageRenderingSelector("#warn-msg li", ":root");
private MessageRenderingSelector messageGlobalErrSelector = new MessageRenderingSelector("#err-msg li", ":root");
private String messageDuplicatorIndicatorAttrName = "afd:message-duplicator";
private Elements cachedSnippet = null;
private ContextBindData<List<MessageHolder>> messageList = new ContextBindData<List<MessageHolder>>(true) {
@Override
protected List<MessageHolder> buildData() {
return new LinkedList<>();
}
};
public DefaultMessageRenderingHelper() {
super();
}
public static DefaultMessageRenderingHelper getConfiguredInstance() {
return (DefaultMessageRenderingHelper) WebApplicationConfiguration.getWebApplicationConfiguration().getMessageRenderingHelper();
}
public String getMessageGlobalContainerParentSelector() {
return messageGlobalContainerParentSelector;
}
public void setMessageGlobalContainerParentSelector(String messageGlobalContainerParentSelector) {
this.messageGlobalContainerParentSelector = messageGlobalContainerParentSelector;
}
public String getMessageGlobalContainerSelector() {
return messageGlobalContainerSelector;
}
public void setMessageGlobalContainerSelector(String messageGlobalContainerSelector) {
this.messageGlobalContainerSelector = messageGlobalContainerSelector;
}
public String getMessageGlobalContainerSnippetFilePath() {
return messageGlobalContainerSnippetFilePath;
}
public void setMessageGlobalContainerSnippetFilePath(String messageGlobalContainerSnippetFilePath) {
this.messageGlobalContainerSnippetFilePath = messageGlobalContainerSnippetFilePath;
}
public MessageRenderingSelector getMessageGlobalInfoSelector() {
return messageGlobalInfoSelector;
}
public void setMessageGlobalInfoSelector(MessageRenderingSelector messageGlobalInfoSelector) {
this.messageGlobalInfoSelector = messageGlobalInfoSelector;
}
public MessageRenderingSelector getMessageGlobalWarnSelector() {
return messageGlobalWarnSelector;
}
public void setMessageGlobalWarnSelector(MessageRenderingSelector messageGlobalWarnSelector) {
this.messageGlobalWarnSelector = messageGlobalWarnSelector;
}
public MessageRenderingSelector getMessageGlobalErrSelector() {
return messageGlobalErrSelector;
}
public void setMessageGlobalErrSelector(MessageRenderingSelector messageGlobalErrSelector) {
this.messageGlobalErrSelector = messageGlobalErrSelector;
}
public String getMessageDuplicatorIndicatorAttrName() {
return messageDuplicatorIndicatorAttrName;
}
public void setMessageDuplicatorIndicatorAttrName(String messageDuplicatorIndicatorAttrName) {
this.messageDuplicatorIndicatorAttrName = messageDuplicatorIndicatorAttrName;
}
public Renderer createMessageRenderer() {
Renderer renderer = Renderer.create();
Renderer message = renderMesssages();
if (message != null) {
renderer.add(message);
}
renderer.add(postMessageRendering());
return renderer;
}
protected Renderer postMessageRendering() {
// remove all the remaining message duplicators which may not be referenced in message outputting, which is why they are remaining
Renderer render = Renderer.create();
render.disableMissingSelectorWarning();
render.add(SelectorUtil.attr(messageDuplicatorIndicatorAttrName), Clear);
render.enableMissingSelectorWarning();
return render;
}
/**
*
* @return Pair.left: whether the alternative message container is necessary <br>
* Pair.right: the actual renderer
*/
protected Renderer renderMesssages() {
List<MessageHolder> allMsgList = new LinkedList<>();
allMsgList.addAll(messageList.get());
if (allMsgList.isEmpty()) {
return null;
}
Renderer renderer = Renderer.create();
final Map<MessageRenderingSelector, List<MessageHolder>> msgMap = new HashMap<>();
final Map<MessageRenderingSelector, List<String>> alternativeMsgMap = new HashMap<>();
List<MessageHolder> tmpList;
for (MessageHolder mh : allMsgList) {
tmpList = msgMap.get(mh.selector);
if (tmpList == null) {
tmpList = new LinkedList<>();
msgMap.put(mh.selector, tmpList);
}
tmpList.add(mh);
}
renderer.disableMissingSelectorWarning();
for (final Entry<MessageRenderingSelector, List<MessageHolder>> item : msgMap.entrySet()) {
if (item.getKey() == null) {
List<String> list;
for (MessageHolder mh : item.getValue()) {
list = alternativeMsgMap.get(mh.alternativeSelector);
if (list == null) {
list = new LinkedList<>();
alternativeMsgMap.put(mh.alternativeSelector, list);
}
list.add(mh.message);
}
} else {
final MessageRenderingSelector selector = item.getKey();
renderer.add(selector.duplicator, item.getValue(), (MessageHolder obj) -> {
Renderer render = Renderer.create(selector.valueTarget, obj.message);
render.add(":root", messageDuplicatorIndicatorAttrName, Clear);
return render;
});
renderer.add(new ElementNotFoundHandler(selector.duplicator) {
@Override
public Renderer alternativeRenderer() {
List<String> list;
for (MessageHolder mh : item.getValue()) {
list = alternativeMsgMap.get(mh.alternativeSelector);
if (list == null) {
list = new LinkedList<>();
alternativeMsgMap.put(mh.alternativeSelector, list);
}
list.add(mh.message);
}
return Renderer.create();
}
});
}
} // end for loop
renderer.enableMissingSelectorWarning();
renderer.add(messageGlobalContainerParentSelector, new Renderable() {
@Override
public Renderer render() {
Renderer renderer = Renderer.create();
if (!alternativeMsgMap.isEmpty()) {
renderer.add(new ElementNotFoundHandler(messageGlobalContainerSelector) {
@Override
public Renderer alternativeRenderer() {
// add global message container if not exists
return Renderer.create(":root", new ElementSetter() {
@Override
public void set(Element elem) {
List<Element> elems = new ArrayList<>(retrieveCachedContainerSnippet());
Collections.reverse(elems);
for (Element child : elems) {
elem.prependChild(child.clone());
}
}
});
}// alternativeRenderer
});// ElementNotFoundHandler
renderer.add(messageGlobalContainerSelector, new Renderable() {
@Override
public Renderer render() {
Renderer alternativeMsgRenderer = Renderer.create();
for (final Entry<MessageRenderingSelector, List<String>> item : alternativeMsgMap.entrySet()) {
final MessageRenderingSelector selector = item.getKey();
alternativeMsgRenderer.add(selector.duplicator, item.getValue(), (String msg) -> {
Renderer render = Renderer.create(selector.valueTarget, msg);
render.add(":root", messageDuplicatorIndicatorAttrName, Clear);
return render;
});
}
return alternativeMsgRenderer;
}
});// messageGlobalContainerSelector
}
return renderer;
}
});
return renderer;
}
protected Elements retrieveCachedContainerSnippet() {
if (WebApplicationConfiguration.getWebApplicationConfiguration().isCacheEnable()) {
if (cachedSnippet == null) {
cachedSnippet = retrieveContainerSnippet();
}
return cachedSnippet;
} else {
return retrieveContainerSnippet();
}
}
protected Elements retrieveContainerSnippet() {
WebApplicationConfiguration conf = WebApplicationConfiguration.getWebApplicationConfiguration();
Template template;
try {
// at first, we treat the configured snippet file as a template file
try {
template = conf.getTemplateResolver().findTemplate(messageGlobalContainerSnippetFilePath);
} catch (TemplateNotFoundException e) {
// then treat it as classpath resource
template = fallbackMessageContainerResolver.findTemplate(messageGlobalContainerSnippetFilePath);
}
return template.getDocumentClone().body().children();
} catch (TemplateException | TemplateNotFoundException e) {
throw new RuntimeException(e);
}
}
public void outputMessage(final MessageRenderingSelector selector, final MessageRenderingSelector alternativeSelector,
final String msg) {
messageList.get().add(new MessageHolder(selector, alternativeSelector, msg));
RedirectUtil.registerRedirectInterceptor(this.getClass().getName() + "#outputMessage", new RedirectInterceptor() {
@Override
public void beforeRedirect() {
List<MessageHolder> list = new ArrayList<>(messageList.get());
if (!list.isEmpty()) {
RedirectUtil.addFlashScopeData(FLASH_MSG_LIST_KEY, list);
}
}
@Override
public void afterRedirectDataRestore() {
List<MessageHolder> flashedList = Context.getCurrentThreadContext().getData(WebApplicationContext.SCOPE_FLASH,
FLASH_MSG_LIST_KEY);
if (flashedList != null) {
messageList.get().addAll(flashedList);
}
}
});
}
private void outputMessage(String duplicator, String msgTargetSelector, MessageRenderingSelector alternativeSelector, String msg) {
MessageRenderingSelector selector = null;
if (duplicator == null) {
// do nothing
} else {
if (msgTargetSelector == null) {
msgTargetSelector = ":root";
}
selector = new MessageRenderingSelector(duplicator, msgTargetSelector);
}
outputMessage(selector, alternativeSelector, msg);
}
public void info(String msg) {
info(null, null, msg);
}
public void info(String selector, String msg) {
info(selector, null, msg);
}
public void info(String duplicator, String msgTargetSelector, String msg) {
outputMessage(duplicator, msgTargetSelector, messageGlobalInfoSelector, msg);
}
public void warn(String msg) {
warn(null, null, msg);
}
public void warn(String selector, String msg) {
warn(selector, null, msg);
}
public void warn(String duplicator, String msgTargetSelector, String msg) {
outputMessage(duplicator, msgTargetSelector, messageGlobalWarnSelector, msg);
}
public void err(String msg) {
err(null, null, msg);
}
public void err(String selector, String msg) {
err(selector, null, msg);
}
public void err(String duplicator, String msgTargetSelector, String msg) {
outputMessage(duplicator, msgTargetSelector, messageGlobalErrSelector, msg);
}
}