package com.anjlab.ping.pages;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;
import javax.cache.Cache;
import org.apache.tapestry5.annotations.AfterRender;
import org.apache.tapestry5.annotations.BeforeRenderTemplate;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.corelib.components.Grid;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.BeanModelSource;
import org.apache.tapestry5.services.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.anjlab.gae.QuotaDetails;
import com.anjlab.gae.QuotaDetails.Quota;
import com.anjlab.ping.entities.Account;
import com.anjlab.ping.entities.Job;
import com.anjlab.ping.entities.Ref;
import com.anjlab.ping.pages.task.UpdateJobsTask;
import com.anjlab.ping.services.Application;
import com.anjlab.ping.services.GAEHelper;
import com.anjlab.ping.services.Utils;
import com.anjlab.tapestry5.AbstractReadonlyPropertyConduit;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
public class Index {
private static final Logger logger = LoggerFactory.getLogger(Index.class);
@Property
@Persist
@SuppressWarnings("unused")
private String message;
@Property
@Persist
@SuppressWarnings("unused")
private String messageColor;
private Boolean quotaLimited;
private Long timeToQuotaRefreshMillis;
@AfterRender
public void cleanup() {
message = null;
userAccount = null;
account = null;
grantedEmail = null;
job = null;
messageColor = null;
quotaLimited = null;
timeToQuotaRefreshMillis = null;
}
@Property
private Job job;
public String getCronString() {
return (!job.isSuspended() ? job.getCronString() : "suspended");
}
public String getLastPingTimestamp() {
Date timestamp = job.getLastPingTimestamp();
return timestamp != null ? application.formatDate(timestamp) : "N/A";
}
public String getSummaryStatusCssClass()
{
switch (job.getHealthStatus())
{
case Error:
return "status-error";
case Unknown:
return "status-na";
case OK:
return "status-okay";
default:
return "status-warning";
}
}
public Long[] getJobContext() {
return Utils.createJobContext(job);
}
public void onActionFromDeleteJob(Long jobId) {
try {
application.deleteJob(jobId, true);
} catch (Exception e) {
operationFailed("Error deleting job", e);
}
}
@Property
private Account account;
public boolean isDeleteAccountLinkEnabled() {
return ! account.getId().equals(getUserAccount().getId());
}
private Account userAccount;
public Account getUserAccount() {
if (userAccount == null) {
String impersonatedUser = request.getParameter("impersonatedUser");
userAccount = impersonatedUser == null
? application.getUserAccount()
: application.getUserAccount(decodeTapestryUrl(impersonatedUser));
}
return userAccount;
}
private String decodeTapestryUrl(String impersonatedUser) {
return impersonatedUser.replaceAll("\\$0040", "@");
}
@Property
private String grantedEmail;
@Property
private boolean readOnly;
public void onSuccessFromGrantAccessTo() {
try {
application.grantAccess(grantedEmail, getUserAccount().getEmail(), readOnly ? Ref.ACCESS_TYPE_READONLY : Ref.ACCESS_TYPE_FULL);
} catch (Exception e) {
operationFailed("Grant failed", e);
}
}
@Inject
private Request request;
public List<Job> getJobs() {
return application.getAvailableJobs(getUserAccount());
}
@Inject
private Application application;
public List<Account> getAccounts() {
return application.getAccounts(getUserAccount().getEmail());
}
public void onActionFromRemoveAccount(Long accountId) {
try {
application.removeAccount(accountId, getUserAccount().getEmail());
} catch (Exception e) {
operationFailed("Remove account failed", e);
}
}
public boolean isAdmin() {
UserService userService = UserServiceFactory.getUserService();
return userService.isUserLoggedIn()
&& userService.isUserAdmin();
}
@Inject
private Cache cache;
@Inject
private MemcacheService memcache;
public void onActionFromClearCache() {
memcache.clearAll();
cache.clear();
operationSucceeded("Cache cleared");
}
public void onActionFromQuotaLimited() {
quotaDetails.setQuotaLimited(Quota.DatastoreWrite, true);
}
@Inject
private QuotaDetails quotaDetails;
private boolean isQuotaLimited() {
if (quotaLimited == null) {
quotaLimited = quotaDetails.isQuotaLimited();
}
return quotaLimited;
}
private long getTimeToQuotaRefreshMillis() {
if (isQuotaLimited() && timeToQuotaRefreshMillis == null) {
timeToQuotaRefreshMillis = quotaDetails.getQuotaLimitExpirationMillis() - System.currentTimeMillis();
}
return timeToQuotaRefreshMillis;
}
public String getSystemMessageClass() {
return isQuotaLimited() ? "system-message" : "";
}
public String getSystemMessage() {
if (!isQuotaLimited()) {
return "";
}
String timeToQuotaRefresh = Utils.formatMillisecondsToWordsUpToMajorUnits(getTimeToQuotaRefreshMillis()).toLowerCase();
String timeToQuotaRefreshMinutes = Utils.formatMillisecondsToWordsUpToMinutes(getTimeToQuotaRefreshMillis()).toLowerCase();
boolean showHint = !timeToQuotaRefresh.equals(timeToQuotaRefreshMinutes);
String hintStartTag = showHint ? "<span title='" + timeToQuotaRefreshMinutes + "' class='system-message-underline'>" : "";
String hintEndTag = showHint ? "</span>" : "";
return "We're sorry, but Ping Service is out of free quota limits right now. " +
"Next quota refresh will be in " + hintStartTag + timeToQuotaRefresh + hintEndTag + ".";
}
private void operationSucceeded(String message) {
this.message = message;
this.messageColor = "green";
}
public void operationFailed(String message, Exception... e) {
this.message = message;
this.messageColor = "red";
if (e != null) {
logger.error(message, e);
}
}
private int counter = 0;
@Component(id="grid")
private Grid grid;
@BeforeRenderTemplate
void beforeRender() {
if (grid.getSortModel().getSortConstraints().isEmpty()) {
// ascending
grid.getSortModel().updateSort("titleFriendly");
}
}
@Inject private BeanModelSource beanModelSource;
@Inject private Messages messages;
public BeanModel<?> getModel() {
BeanModel<?> beanModel = beanModelSource.createDisplayModel(Job.class, messages);
beanModel.add("SN", new AbstractReadonlyPropertyConduit()
{
@Override
public Object get(Object instance) {
return ++counter;
}
}).sortable(false);
beanModel.add("actions", null);
beanModel.exclude(
"estimatedSerializedSize",
"receiveNotifications",
"pingURL",
"createdAt",
"lastBackupTimestamp",
"statusCounter",
"statusCounterFriendlyShort",
"recentAvailabilityPercentFriendly",
"totalAvailabilityPercentFriendly",
"googleIOException",
"totalStatusCounter",
"totalSuccessStatusCounter",
"previousStatusCounter",
"totalStatusCounterFriendly",
"totalSuccessStatusCounterFriendly",
"previousStatusCounterFriendly",
"title",
"shortenURL",
"lastPingFailed",
"responseEncoding",
"validatingRegexp",
"validatingHttpCode",
"lastPingDetails",
"reportEmail",
"lastPingResult",
"usesValidatingRegexp",
"usesValidatingHttpCode",
"lastPingTimestamp",
"statusCounterFriendly",
"receiveBackups",
"validationSummary",
"resultsCount",
"modifiedAt",
"modifiedBy",
"healthStatus",
"suspended",
"suspendReason",
"suspendedAt",
"suspendedBy",
"lastPingWasTooLongAgo",
"scheduledBy",
"scheduleName");
beanModel.reorder(
"SN",
"titleFriendly",
"cronString",
"lastPingSummary",
"upDownTimeInMinutes",
"recentAvailabilityPercent",
"totalAvailabilityPercent",
"actions");
return beanModel;
}
public void onActionFromUdpateQuotas() throws URISyntaxException {
GAEHelper.addTaskNonTransactional(QueueFactory.getQueue(Application.DEFAULT_QUEUE),
application.buildTaskUrl(UpdateJobsTask.class));
}
}