/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.controls.ads;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.servlet.http.HttpServletRequest;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.controls.ActionContext;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.Currency;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.ads.Ad;
import nl.strohalm.cyclos.entities.ads.AdCategory;
import nl.strohalm.cyclos.entities.customization.fields.AdCustomField;
import nl.strohalm.cyclos.entities.customization.fields.AdCustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldValue;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.groups.MemberGroupSettings;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.Operator;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.entities.settings.events.LocalSettingsEvent;
import nl.strohalm.cyclos.services.accounts.AccountTypeService;
import nl.strohalm.cyclos.services.accounts.CurrencyService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.ImageHelper.ImageType;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.TextFormat;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.binding.BeanBinder;
import nl.strohalm.cyclos.utils.binding.BeanCollectionBinder;
import nl.strohalm.cyclos.utils.binding.DataBinder;
import nl.strohalm.cyclos.utils.binding.DataBinderHelper;
import nl.strohalm.cyclos.utils.binding.PropertyBinder;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.conversion.HtmlConverter;
import nl.strohalm.cyclos.utils.conversion.IdConverter;
import nl.strohalm.cyclos.utils.conversion.ReferenceConverter;
import nl.strohalm.cyclos.utils.conversion.StringTrimmerConverter;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.struts.action.ActionForward;
import org.apache.struts.upload.FormFile;
/**
* Action used to edit an advertisement
* @author luis
*/
public class EditAdAction extends BaseAdAction {
private AccountTypeService accountTypeService;
private CurrencyService currencyService;
private DataBinder<Ad> writeDataBinder;
private ReadWriteLock lock = new ReentrantReadWriteLock(true);
// Used to get data and save to database
public DataBinder<Ad> getWriteDataBinder() {
try {
lock.readLock().lock();
if (writeDataBinder == null) {
final LocalSettings settings = settingsService.getLocalSettings();
final BeanBinder<? extends CustomFieldValue> customValueBinder = BeanBinder.instance(AdCustomFieldValue.class);
customValueBinder.registerBinder("field", PropertyBinder.instance(AdCustomField.class, "field", ReferenceConverter.instance(AdCustomField.class)));
customValueBinder.registerBinder("value", PropertyBinder.instance(String.class, "value", HtmlConverter.instance()));
final BeanBinder<Ad> binder = BeanBinder.instance(Ad.class);
binder.registerBinder("id", PropertyBinder.instance(Long.class, "id", IdConverter.instance()));
binder.registerBinder("owner", PropertyBinder.instance(Member.class, "owner", ReferenceConverter.instance(Member.class)));
binder.registerBinder("tradeType", PropertyBinder.instance(Ad.TradeType.class, "tradeType"));
binder.registerBinder("category", PropertyBinder.instance(AdCategory.class, "category", ReferenceConverter.instance(AdCategory.class)));
binder.registerBinder("title", PropertyBinder.instance(String.class, "title"));
binder.registerBinder("externalPublication", PropertyBinder.instance(Boolean.TYPE, "externalPublication"));
binder.registerBinder("permanent", PropertyBinder.instance(Boolean.TYPE, "permanent"));
binder.registerBinder("publicationPeriod", DataBinderHelper.rawPeriodBinder(settings, "publicationPeriod"));
binder.registerBinder("currency", PropertyBinder.instance(Currency.class, "currency"));
binder.registerBinder("price", PropertyBinder.instance(BigDecimal.class, "price", settings.getNumberConverter()));
binder.registerBinder("html", PropertyBinder.instance(Boolean.TYPE, "html"));
binder.registerBinder("customValues", BeanCollectionBinder.instance(customValueBinder, "customValues"));
writeDataBinder = binder;
}
return writeDataBinder;
} finally {
lock.readLock().unlock();
}
}
@Override
public void onLocalSettingsUpdate(final LocalSettingsEvent event) {
try {
lock.writeLock().lock();
super.onLocalSettingsUpdate(event);
writeDataBinder = null;
} finally {
lock.writeLock().unlock();
}
}
@Inject
public void setAccountTypeService(final AccountTypeService accountTypeService) {
this.accountTypeService = accountTypeService;
}
@Inject
public void setCurrencyService(final CurrencyService currencyService) {
this.currencyService = currencyService;
}
/**
* gets the member
*/
protected Member getMember(final ActionContext context) {
Member member;
final AdForm form = context.getForm();
final long adId = form.getId();
if (adId > 0) {
return getAdService().load(adId, Ad.Relationships.OWNER).getOwner();
}
final Element loggedElement = context.getElement();
if (form.getMemberId() <= 0 || form.getMemberId() == loggedElement.getId()) {
if (context.isAdmin()) {
throw new ValidationException();
}
if (context.isOperator()) {
member = ((Operator) context.getElement()).getMember();
} else { // context.isMember()
member = context.getElement();
}
} else {
final Element element = elementService.load(form.getMemberId(), Element.Relationships.USER);
if (!(element instanceof Member)) {
throw new ValidationException();
}
member = (Member) element;
}
member = elementService.load(member.getId(), Element.Relationships.GROUP);
return member;
}
/**
* gets the number of ads for this member
* @return an int indicating the number of ads
*/
protected int getNumberOfAds(final Member member) {
final Map<Ad.Status, Integer> adMap = getAdService().getNumberOfAds(null, member);
final Collection<Integer> values = adMap.values();
int totalAds = 0;
for (final Integer i : values) {
totalAds += i;
}
return totalAds;
}
@Override
protected ActionForward handleDisplay(final ActionContext context) throws Exception {
final HttpServletRequest request = context.getRequest();
final AdForm form = context.getForm();
final ActionForward forward = super.handleDisplay(context);
final Ad ad = (Ad) request.getAttribute("ad");
TextFormat descriptionFormat;
if (ad.isTransient()) {
final LocalSettings localSettings = settingsService.getLocalSettings();
descriptionFormat = localSettings.getAdDescriptionFormat();
form.setAd("html", descriptionFormat == TextFormat.RICH);
} else {
descriptionFormat = ad.isHtml() ? TextFormat.RICH : TextFormat.PLAIN;
}
request.setAttribute("descriptionFormat", descriptionFormat);
final boolean editable = (Boolean) request.getAttribute("editable");
final MemberGroup memberGroup = ad.getOwner().getMemberGroup();
final List<Currency> currencies = currencyService.listByMemberGroup(memberGroup);
request.setAttribute("currencies", currencies);
if (editable) {
if (currencies.size() == 1) {
// Set a single currency variable when there's only one option
request.setAttribute("singleCurrency", currencies.get(0));
} else if (currencies.size() > 1 && ad.getCurrency() == null) {
// When there's multiple currencies, pre select the one of the default account
final MemberAccountType defaultAccountType = accountTypeService.getDefault(memberGroup, AccountType.Relationships.CURRENCY);
if (defaultAccountType != null) {
form.setAd("currency", CoercionHelper.coerce(String.class, defaultAccountType.getCurrency()));
}
}
final Member member = getMember(context);
final MemberGroupSettings memberSettings = member.getMemberGroup().getMemberSettings();
// Check if more ads can be added
final int adCount = getNumberOfAds(member);
final int maxAdsPerMember = memberSettings.getMaxAdsPerMember();
final boolean maxAds = (adCount >= maxAdsPerMember);
request.setAttribute("maxAds", maxAds);
// Store the restrictions
request.setAttribute("enablePermanent", memberSettings.isEnablePermanentAds());
request.setAttribute("enableExternalPublication", memberSettings.getExternalAdPublication() == MemberGroupSettings.ExternalAdPublication.ALLOW_CHOICE);
return forward;
} else {
// Non-editable ads cannot change currency: use the first one
request.setAttribute("singleCurrency", currencies.isEmpty() ? null : currencies.get(0));
return ActionHelper.redirectWithParam(request, context.findForward("view"), "id", form.getId());
}
}
@Override
protected ActionForward handleSubmit(final ActionContext context) throws Exception {
final AdForm form = context.getForm();
Ad ad = readAd(context);
final boolean isInsert = ad.isTransient();
// Save the advertisement
ad = getAdService().save(ad);
// Save the uploaded image
final FormFile upload = form.getPicture();
if (upload != null && upload.getFileSize() > 0) {
try {
StringBuffer newFileName = new StringBuffer(upload.getFileName());
if (upload.getFileName().length() > 100) {
// File name cannot be greater than 100 characters
newFileName = new StringBuffer();
final String name = upload.getFileName();
final int extensionPos = name.lastIndexOf(".");
final String extension = name.substring(extensionPos);
newFileName.append(name.substring(0, 100 - extension.length()));
newFileName.append(extension);
}
getImageService().save(ad, form.getPictureCaption(), ImageType.getByContentType(upload.getContentType()), newFileName.toString(), upload.getInputStream());
} finally {
upload.destroy();
}
}
context.sendMessage(isInsert ? "ad.inserted" : "ad.modified");
final Map<String, Object> params = new HashMap<String, Object>();
params.put("id", ad.getId());
params.put("memberId", ad.getOwner().getId());
return ActionHelper.redirectWithParams(context.getRequest(), context.getSuccessForward(), params);
}
@Override
protected Ad resolveAd(final ActionContext context) throws Exception {
final AdForm form = context.getForm();
if (form.getId() > 0L) {
// Edit an ad - the superclass will read an existing ad
return super.resolveAd(context);
} else {
// Insert a new ad
Member member;
final Element loggedElement = context.getElement();
if (form.getMemberId() > 0L && form.getMemberId() != loggedElement.getId()) {
final Element element = elementService.load(form.getMemberId(), Element.Relationships.GROUP);
if (!(element instanceof Member)) {
throw new ValidationException();
}
member = (Member) element;
} else {
if (context.isAdmin()) {
throw new ValidationException();
} else if (context.isMember()) {
member = context.getElement();
} else { // context.isOperator()
member = ((Operator) context.getElement()).getMember();
}
}
final MemberGroup group = member.getMemberGroup();
final MemberGroupSettings settings = group.getMemberSettings();
final TimePeriod defaultPublicationTime = (settings == null ? null : settings.getDefaultAdPublicationTime());
// Set the default values
final Ad ad = new Ad();
ad.setOwner(member);
ad.setTradeType(Ad.TradeType.OFFER);
final Calendar today = Calendar.getInstance();
ad.setPublicationPeriod(Period.between(today, defaultPublicationTime.add(today)));
return ad;
}
}
@Override
protected void validateForm(final ActionContext context) {
final Ad ad = readAd(context);
getAdService().validate(ad);
}
private Ad readAd(final ActionContext context) {
final AdForm form = context.getForm();
final Ad ad = getWriteDataBinder().readFromString(form.getAd());
if (ad.isHtml()) {
ad.setDescription(HtmlConverter.instance().valueOf("" + form.getAd("description")));
} else {
ad.setDescription(StringTrimmerConverter.instance().valueOf("" + form.getAd("description")));
}
if (ad.getOwner() == null) {
if (context.isMember()) {
ad.setOwner((Member) context.getElement());
} else if (context.isOperator()) {
final Operator operator = (Operator) context.getElement();
ad.setOwner(operator.getMember());
}
}
return ad;
}
}