package act.util; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.Destroyable; import act.app.ActionContext; import act.app.App; import act.cli.CliContext; import act.conf.AppConfig; import act.i18n.I18n; import act.mail.MailerContext; import act.view.Template; import org.osgl.$; import org.osgl.http.H; import org.osgl.mvc.util.ParamValueProvider; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import javax.enterprise.context.RequestScoped; import javax.validation.ConstraintViolation; import java.util.*; import static act.app.App.LOGGER; public interface ActContext<CTX_TYPE extends ActContext> extends ParamValueProvider { App app(); AppConfig config(); CTX_TYPE accept(H.Format fmt); H.Format accept(); CTX_TYPE locale(Locale locale); Locale locale(); Locale locale(boolean required); /** * If {@link #templatePath(String) template path has been set before} then return * the template path */ String templatePath(); /** * Set path to template file * @param path the path to template file * @return this {@code AppContext} */ CTX_TYPE templatePath(String path); /** * Returns the template context * @return template context */ String templateContext(); /** * Set template context * @param context the path to template context * @return this {@code ActContext} */ CTX_TYPE templateContext(String context); /** * Check if the template path is implicit i.e. derived from {@link #methodPath()} * @return `true` if template path is implicit; `false` otherwise */ boolean templatePathIsImplicit(); Template cachedTemplate(); CTX_TYPE cacheTemplate(Template template); <T> T renderArg(String name); /** * Returns all render arguments */ Map<String, Object> renderArgs(); CTX_TYPE renderArg(String name, Object val); CTX_TYPE addListener(Listener listener); CTX_TYPE addDestroyable(Destroyable resource); CTX_TYPE attribute(String name, Object attr); <T> T attribute(String name); Map<String, Object> attributes(); CTX_TYPE removeAttribute(String name); CTX_TYPE addViolations(Map<String, ConstraintViolation> violations); CTX_TYPE addViolation(String property, ConstraintViolation violation); boolean hasViolation(); Map<String, ConstraintViolation> violations(); ConstraintViolation violation(String property); String i18n(boolean ignoreError, String msgId, Object... args); String i18n(String msgId, Object ... args); String i18n(Class<?> bundleSpec, String msgId, Object... args); String i18n(boolean ignoreError, Class<?> bundleSpec, String msgId, Object... args); String i18n(Enum<?> msgId); String i18n(Class<?> bundleSpec, Enum<?> msgId); Map<String, Object> i18n(Class<? extends Enum> enumClass); Map<String, Object> i18n(Class<?> bundleSpec, Class<? extends Enum> enumClass); Map<String, Object> i18n(Class<? extends Enum> enumClass, boolean outputProperties); Map<String, Object> i18n(Class<?> bundleSpec, Class<? extends Enum> enumClass, boolean outputProperties); String methodPath(); /** * Returns a reusable {@link S.Buffer} instance * @return an S.Buffer instance that can be reused */ S.Buffer strBuf(); interface Listener { void onDestroy(ActContext context); } abstract class Base<CTX extends Base> extends DestroyableBase implements ActContext<CTX> { private App app; private String templatePath; private String templateContext; private Template template; private Map<String, Object> renderArgs; private List<Listener> listenerList; private List<Destroyable> destroyableList; private Map<String, Object> attributes; private Locale locale; private S.Buffer strBuf; // (violation.propertyPath, violation) private Map<String, ConstraintViolation> violations; public Base(App app) { E.NPE(app); this.app = app; renderArgs = new HashMap<>(); attributes = new HashMap<>(); listenerList = new ArrayList<>(); destroyableList = new ArrayList<>(); strBuf = S.newBuffer(); violations = new HashMap<>(); } @Override protected void releaseResources() { for (Listener l : listenerList) { try { l.onDestroy(this); } catch (Exception e) { LOGGER.warn(e, "error calling listener onDestroy method"); } } Destroyable.Util.destroyAll(destroyableList, RequestScoped.class); Destroyable.Util.tryDestroyAll(attributes.values(), RequestScoped.class); this.attributes.clear(); this.renderArgs.clear(); this.template = null; this.app = null; this.template = null; this.listenerList.clear(); this.destroyableList.clear(); this.violations.clear(); } @Override public App app() { return app; } @Override public AppConfig config() { return app().config(); } @Override public String templatePath() { String path = templatePath; String context = templateContext; if (S.notBlank(path)) { return path.startsWith("/") || S.blank(context) ? path : S.pathConcat(context, '/', path); } else { if (S.blank(context)) { return methodPath().replace('.', '/'); } else { return S.pathConcat(context, '/', S.afterLast(methodPath(), ".")); } } } @Override public CTX templatePath(String templatePath) { this.template = null; this.templatePath = templatePath; return me(); } public String templateContext() { return this.templateContext; } public CTX templateContext(String templateContext) { this.template = null; this.templateContext = templateContext; return me(); } /** * Template path is implicit if {@link #templatePath(String)} is never called * on this context instance * @return `true` if template path is implicit */ @Override public boolean templatePathIsImplicit() { return null == templatePath; } @Override public Template cachedTemplate() { return template; } @Override public CTX cacheTemplate(Template template) { this.template = template; return me(); } public final CTX locale(Locale locale) { this.locale = locale; return me(); } public final Locale locale() { return this.locale; } public Locale locale(boolean required) { Locale locale = this.locale; if (null == locale) { if (!required) { return null; } locale = config().locale(); } return locale; } public static final String DEF_RESOURCE_BUNDLE_NAME = I18n.DEF_RESOURCE_BUNDLE_NAME; public String i18n(boolean ignoreError, String msgId, Object... args) { return I18n.i18n(ignoreError, locale(true), I18n.DEF_RESOURCE_BUNDLE_NAME, msgId, args); } public String i18n(String msgId, Object ... args) { return I18n.i18n(locale(true), DEF_RESOURCE_BUNDLE_NAME, msgId, args); } public String i18n(Class<?> bundleSpec, String msgId, Object... args) { return I18n.i18n(locale(true), bundleSpec.getName(), msgId, args); } public String i18n(boolean ignoreError, Class<?> bundleSpec, String msgId, Object... args) { return I18n.i18n(ignoreError, locale(true), bundleSpec.getName(), msgId, args); } public String i18n(Enum<?> msgId) { return I18n.i18n(locale(true), DEF_RESOURCE_BUNDLE_NAME, msgId); } public String i18n(Class<?> bundleSpec, Enum<?> msgId) { return I18n.i18n(locale(true), bundleSpec.getName(), msgId); } public Map<String, Object> i18n(Class<? extends Enum> enumClass) { return I18n.i18n(locale(true), enumClass); } public Map<String, Object> i18n(Class<?> bundleSpec, Class<? extends Enum> enumClass) { return I18n.i18n(locale(true), bundleSpec, enumClass); } public Map<String, Object> i18n(Class<? extends Enum> enumClass, boolean outputPropeties) { return I18n.i18n(locale(true), enumClass, outputPropeties); } public Map<String, Object> i18n(Class<?> bundleSpec, Class<? extends Enum> enumClass, boolean outputProperties) { return I18n.i18n(locale(true), bundleSpec, enumClass, outputProperties); } protected CTX me() { return (CTX) this; } @Override public <T> T renderArg(String name) { return (T) renderArgs.get(name); } @Override public CTX renderArg(String name, Object val) { renderArgs.put(name, val); return me(); } @Override public Map<String, Object> renderArgs() { return C.newMap(renderArgs); } /** * Associate a user attribute to the context. Could be used by third party * libraries or user application * * @param name the className used to reference the attribute * @param attr the attribute object * @return this context */ public CTX attribute(String name, Object attr) { attributes.put(name, attr); return me(); } public <T> T attribute(String name) { return $.cast(attributes.get(name)); } public CTX removeAttribute(String name) { attributes.remove(name); return me(); } @Override public Map<String, Object> attributes() { return attributes; } @Override public CTX addListener(Listener listener) { listenerList.add(listener); return me(); } @Override public CTX addDestroyable(Destroyable resource) { destroyableList.add(resource); return me(); } @Override public S.Buffer strBuf() { return strBuf.consumed() ? strBuf.reset() : S.newBuffer(); } @Override public CTX addViolations(Map<String, ConstraintViolation> violations) { this.violations.putAll(violations); return me(); } @Override public CTX addViolation(String property, ConstraintViolation violation) { this.violations.put(property, violation); return me(); } @Override public boolean hasViolation() { return !violations.isEmpty(); } @Override public Map<String, ConstraintViolation> violations() { return C.map(this.violations); } @Override public ConstraintViolation violation(String property) { return this.violations.get(property); } public static ActContext currentContext() { ActContext ctx = ActionContext.current(); if (null != ctx) { return ctx; } ctx = MailerContext.current(); if (null != ctx) { return ctx; } ctx = CliContext.current(); if (null != ctx) { return ctx; } return null; } public static Class<? extends ActContext> currentContextType() { ActContext ctx = currentContext(); return null == ctx ? null : ctx.getClass(); } } }