@@ -33,6 +33,14 @@ public class AppMessageDAO extends AccountOwnedTemplateDAO<AppMessage> { | |||
return messages.isEmpty() ? null : messages.get(0); | |||
} | |||
public AppMessage findByAccountAndAppBestEffort (String account, String app, String locale) { | |||
AppMessage message = findByAccountAndAppAndLocale(account, app, locale); | |||
if (message != null) return message; | |||
message = findByAccountAndAppAndDefaultLocale(account, app); | |||
if (message != null) return message; | |||
return findByAccountAndAppAndHighestPriority(account, app); | |||
} | |||
public List<AppMessage> findByApp(String app) { return findByField("app", app); } | |||
} |
@@ -23,8 +23,10 @@ public class AppDataConfig { | |||
public boolean hasFields () { return !empty(fields); } | |||
@Getter @Setter private EntityFieldConfig[] params; | |||
public boolean hasParams () { return !empty(params); } | |||
@Getter @Setter private AppDataAction[] actions; | |||
public boolean hasActions () { return !empty(actions); } | |||
@Getter @Setter private AppDataView[] views; | |||
public boolean hasViews () { return !empty(views); } | |||
@@ -42,7 +42,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||
}) | |||
public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTemplate { | |||
private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled"}; | |||
private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled", "dataConfig"}; | |||
public BubbleApp(Account account, BubbleApp app) { | |||
copy(this, app); | |||
@@ -2,6 +2,7 @@ package bubble.resources.message; | |||
import bubble.model.account.Account; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.service.message.AppMessageService; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.lang.ArrayUtils; | |||
import org.cobbzilla.util.collection.ArrayUtil; | |||
@@ -34,10 +35,24 @@ public class MessagesResource { | |||
public static final String RESOURCE_MESSAGES_PROPS = "ResourceMessages.properties"; | |||
public static final String[] PRE_AUTH_MESSAGE_GROUPS = {"pre_auth", "countries", "timezones"}; | |||
public static final String[] ALL_MESSAGE_GROUPS = ArrayUtil.append(PRE_AUTH_MESSAGE_GROUPS, "post_auth"); | |||
public static final String APPS_MESSAGE_GROUP = "apps"; | |||
public static final String[] ALL_MESSAGE_GROUPS | |||
= ArrayUtil.append(PRE_AUTH_MESSAGE_GROUPS, "post_auth", APPS_MESSAGE_GROUP); | |||
@Autowired private AppMessageService appMessageService; | |||
@Autowired private BubbleConfiguration configuration; | |||
private Map<String, Map<String, String>> messageCache = new ConcurrentHashMap<>(); | |||
@DELETE | |||
public Response flushMessageCache (@Context ContainerRequest ctx) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin()) return forbidden(); | |||
messageCache.clear(); | |||
return ok_empty(); | |||
} | |||
@GET @Path("/{locale}/{group}") | |||
public Response loadMessagesByGroup(@Context ContainerRequest ctx, | |||
@PathParam("locale") String locale, | |||
@@ -59,22 +74,29 @@ public class MessagesResource { | |||
for (String loc : locales) { | |||
try { | |||
return ok_utf8(loadMessages(loc, group, format)); | |||
return ok_utf8(loadMessages(caller, loc, group, format)); | |||
} catch (Exception e) { | |||
log.debug("loadMessagesByGroup: error loading group "+group+" for locale "+loc+": "+e); | |||
log.debug("loadMessagesByGroup: error loading group "+group+" for locale "+loc+": "+e, e); | |||
} | |||
} | |||
log.error("loadMessagesByGroup: error loading group "+group+" for any locale: "+locales); | |||
return notFound(locale+"/"+group); | |||
} | |||
private Map<String, Map<String, String>> messageCache = new ConcurrentHashMap<>(); | |||
private Map<String, String> loadMessages(Account caller, String locale, String group, MessageResourceFormat format) throws IOException { | |||
private Map<String, String> loadMessages(String locale, String group, MessageResourceFormat format) throws IOException { | |||
final String cacheKey = locale+"/"+group+"/"+format; | |||
final boolean isAppsGroup = group.equalsIgnoreCase(APPS_MESSAGE_GROUP); | |||
if (isAppsGroup && caller == null) return Collections.emptyMap(); | |||
final String cacheKey = (isAppsGroup ? caller.getUuid()+"/" : "") + locale + "/" + group + "/" + format; | |||
if (!messageCache.containsKey(cacheKey)) { | |||
final Properties props = new Properties(); | |||
props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); | |||
final Properties props; | |||
if (isAppsGroup) { | |||
props = appMessageService.loadAppMessages(caller, locale); | |||
} else { | |||
props = new Properties(); | |||
props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); | |||
} | |||
final Map<String, String> messages = new LinkedHashMap<>(); | |||
props.forEach((key, value) -> messages.put(format.format(key.toString()), value.toString())); | |||
messageCache.put(cacheKey, messages); | |||
@@ -0,0 +1,88 @@ | |||
package bubble.service.message; | |||
import bubble.dao.app.AppMessageDAO; | |||
import bubble.dao.app.BubbleAppDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.app.*; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.collection.NameAndValue; | |||
import org.cobbzilla.wizard.model.entityconfig.EntityFieldConfig; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
@Service @Slf4j | |||
public class AppMessageService { | |||
public static final String MSG_PREFIX_APP = "app."; | |||
public static final String MSG_SUFFIX_DESCRIPTION = "description"; | |||
public static final String MSG_SUFFIX_NAME = "name"; | |||
public static final String MSG_SUFFIX_FIELD = "field."; | |||
public static final String MSG_SUFFIX_PARAM = "param."; | |||
public static final String MSG_SUFFIX_VIEW = "view."; | |||
public static final String MSG_SUFFIX_ACTION = "action."; | |||
@Autowired private BubbleAppDAO appDAO; | |||
@Autowired private AppMessageDAO appMessageDAO; | |||
public Properties loadAppMessages(Account account, String locale) { | |||
final Properties props = new Properties(); | |||
for (BubbleApp app : appDAO.findByAccount(account.getUuid())) { | |||
final AppMessage appMessage = appMessageDAO.findByAccountAndAppBestEffort(account.getUuid(), app.getUuid(), locale); | |||
final Map<String, String> messages = NameAndValue.toMap(appMessage == null ? null : appMessage.getMessages()); | |||
final String msgPrefix = MSG_PREFIX_APP + app.getName() + "."; | |||
for (Map.Entry<String, String> message : messages.entrySet()) { | |||
props.setProperty(msgPrefix+message.getKey(), message.getValue()); | |||
} | |||
// Check for 'name' message, if not found use default | |||
final String nameKey = msgPrefix + MSG_SUFFIX_NAME; | |||
if (!props.containsKey(nameKey)) props.setProperty(nameKey, app.getName()); | |||
// Check for 'description' message, if not found use default | |||
final String descriptionKey = msgPrefix + MSG_SUFFIX_DESCRIPTION; | |||
if (!props.containsKey(descriptionKey)) props.setProperty(descriptionKey, app.getDescription()); | |||
if (app.hasDataConfig()) { | |||
final AppDataConfig cfg = app.getDataConfig(); | |||
// Check for field messages | |||
if (cfg.hasFields()) { | |||
for (String field : cfg.getFields()) { | |||
final String fieldKey = msgPrefix + MSG_SUFFIX_FIELD + field; | |||
if (!props.containsKey(fieldKey)) props.setProperty(fieldKey, field); | |||
} | |||
} | |||
// Check for view messages | |||
if (cfg.hasViews()) { | |||
for (AppDataView view : cfg.getViews()) { | |||
final String viewKey = msgPrefix + MSG_SUFFIX_VIEW + view.getName(); | |||
if (!props.containsKey(viewKey)) props.setProperty(viewKey, view.getName()); | |||
} | |||
} | |||
// Check for param messages | |||
if (cfg.hasParams()) { | |||
for (EntityFieldConfig param : cfg.getParams()) { | |||
final String paramKey = msgPrefix + MSG_SUFFIX_PARAM + param.getName(); | |||
if (!props.containsKey(paramKey)) props.setProperty(paramKey, param.getName()); | |||
} | |||
} | |||
// Check for action messages | |||
if (cfg.hasActions()) { | |||
for (AppDataAction action : cfg.getActions()) { | |||
final String actionKey = msgPrefix + MSG_SUFFIX_ACTION + action.getName(); | |||
if (!props.containsKey(actionKey)) props.setProperty(actionKey, action.getName()); | |||
} | |||
} | |||
} | |||
} | |||
return props; | |||
} | |||
} |
@@ -38,6 +38,7 @@ | |||
<logger name="bubble.service.notify.NotificationService" level="WARN" /> | |||
<logger name="bubble.resources.stream" level="DEBUG" /> | |||
<logger name="bubble.service.stream" level="INFO" /> | |||
<logger name="bubble.resources.message" level="DEBUG" /> | |||
<logger name="org.cobbzilla.util.io.regex.RegexFilterReader" level="DEBUG" /> | |||
<logger name="org.cobbzilla.util.io.multi" level="INFO" /> | |||
<logger name="bubble.rule.social.block" level="INFO" /> | |||
@@ -13,7 +13,7 @@ | |||
{"name": "most_recent"}, | |||
{"name": "today"}, | |||
{"name": "last_24_hours"}, | |||
{"name": "last_week"}, | |||
{"name": "last_7_days"}, | |||
{"name": "last_30_days"} | |||
] | |||
}, | |||
@@ -33,13 +33,16 @@ | |||
"AppMessage": [{ | |||
"locale": "en_US", | |||
"messages": [ | |||
{"name": "app.description", "value": "Traffic analytics for your Bubble. Manage block lists."}, | |||
{"name": "name", "value": "Traffic Analytics"}, | |||
{"name": "description", "value": "Traffic analytics for your Bubble. Manage block lists."}, | |||
{"name": "field.key", "value": "URL"}, | |||
{"name": "field.data", "value": "Count"}, | |||
{"name": "param.site", "value": "Site"}, | |||
{"name": "view.most_recent", "value": "Most Recent Traffic"}, | |||
{"name": "view.today", "value": "Today"}, | |||
{"name": "view.last_24_hours", "value": "Last 24 Hours"}, | |||
{"name": "view.last_week", "value": "Last Week"}, | |||
{"name": "view.last_30_days", "value": "LAst 30 Days"} | |||
{"name": "view.last_7_days", "value": "Last 7 Days"}, | |||
{"name": "view.last_30_days", "value": "Last 30 Days"} | |||
] | |||
}] | |||
} |
@@ -20,8 +20,14 @@ | |||
"AppMessage": [{ | |||
"locale": "en_US", | |||
"messages": [ | |||
{"name": "app.description", "value": "User Blocker"}, | |||
{"name": "view.blocked_users", "value": "Blocked Users"} | |||
{"name": "name", "value": "User Blocker"}, | |||
{"name": "description", "value": "Throw the garbage to the curb!"}, | |||
{"name": "view.blocked_users", "value": "Blocked Users"}, | |||
{"name": "field.key", "value": "Username"}, | |||
{"name": "field.enabled", "value": "Enforce Block"}, | |||
{"name": "action.enable", "value": "Enforce Block"}, | |||
{"name": "action.disable", "value": "Disable Block"}, | |||
{"name": "action.delete", "value": "Delete Block"} | |||
] | |||
}] | |||
} |
@@ -1 +1 @@ | |||
Subproject commit cf9ff75d0eabf7c3d68cb6ca19fd70b74103b70a | |||
Subproject commit 75079a27bbda0fdf72480e79332be960c4cd0676 |