/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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 this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////
package org.projectforge.web.wicket;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.OddEvenItem;
import org.apache.wicket.markup.repeater.data.IDataProvider;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.projectforge.common.DateFormatType;
import org.projectforge.common.DateFormats;
import org.projectforge.core.AbstractBaseDO;
import org.projectforge.core.BaseDao;
import org.projectforge.core.DisplayHistoryEntry;
import org.projectforge.core.ExtendedBaseDO;
import org.projectforge.core.ModificationStatus;
import org.projectforge.user.UserGroupCache;
import org.projectforge.web.admin.WizardPage;
import org.projectforge.web.calendar.DateTimeFormatter;
import org.projectforge.web.task.TaskTreePage;
import org.projectforge.web.user.UserFormatter;
import org.projectforge.web.user.UserPropertyColumn;
import org.projectforge.web.wicket.flowlayout.DiffTextPanel;
public abstract class AbstractEditPage<O extends AbstractBaseDO< ? >, F extends AbstractEditForm<O, ? >, D extends BaseDao<O>> extends
AbstractSecuredPage implements IEditPage<O, D>
{
private static final long serialVersionUID = 8283877351980165438L;
public static final String PARAMETER_KEY_ID = "id";
public static final String PARAMETER_KEY_DATA_PRESET = "__data";
protected F form;
protected List<DisplayHistoryEntry> historyEntries;
protected boolean showHistory = getBaseDao().isHistorizable();
protected boolean showModificationTimes = true;
protected String i18nPrefix;
protected WebMarkupContainer topMenuPanel;
protected WebMarkupContainer bottomPanel;
@SpringBean(name = "userGroupCache")
protected UserGroupCache userGroupCache;
@SpringBean(name = "userFormatter")
protected UserFormatter userFormatter;
@SpringBean(name = "dateTimeFormatter")
protected DateTimeFormatter dateTimeFormatter;
protected EditPageSupport<O, D> editPageSupport;
public AbstractEditPage(final PageParameters parameters, final String i18nPrefix)
{
super(parameters);
this.i18nPrefix = i18nPrefix;
}
protected void init()
{
init(null);
}
@SuppressWarnings({ "serial", "unchecked"})
protected void init(O data)
{
final StringBuffer buf = new StringBuffer();
buf.append("function showDeleteQuestionDialog() {\n").append(" return window.confirm('");
if (getBaseDao().isHistorizable() == true) {
buf.append(getString("question.markAsDeletedQuestion"));
} else {
buf.append(getString("question.deleteQuestion"));
}
buf.append("');\n}\n");
body.add(new Label("showDeleteQuestionDialog", buf.toString()).setEscapeModelStrings(false));
final Integer id = WicketUtils.getAsInteger(getPageParameters(), PARAMETER_KEY_ID);
if (data == null) {
if (id != null) {
data = getBaseDao().getById(id);
}
if (data == null) {
data = (O) WicketUtils.getAsObject(getPageParameters(), PARAMETER_KEY_DATA_PRESET, getBaseDao().newInstance().getClass());
if (data == null) {
data = getBaseDao().newInstance();
}
}
}
form = newEditForm(this, data);
body.add(form);
form.init();
if (form.isNew() == true) {
showHistory = false;
showModificationTimes = false;
}
body.add(new Label("tabTitle", getTitle()).setRenderBodyOnly(true));
final List<IColumn<DisplayHistoryEntry, String>> columns = new ArrayList<IColumn<DisplayHistoryEntry, String>>();
final CellItemListener<DisplayHistoryEntry> cellItemListener = new CellItemListener<DisplayHistoryEntry>() {
public void populateItem(final Item<ICellPopulator<DisplayHistoryEntry>> item, final String componentId,
final IModel<DisplayHistoryEntry> rowModel)
{
// Later a link should show the history entry as popup.
item.add(AttributeModifier.append("class", new Model<String>("notrlink")));
}
};
final DatePropertyColumn<DisplayHistoryEntry> timestampColumn = new DatePropertyColumn<DisplayHistoryEntry>(dateTimeFormatter,
getString("timestamp"), null, "timestamp", cellItemListener);
timestampColumn.setDatePattern(DateFormats.getFormatString(DateFormatType.DATE_TIME_SHORT_MINUTES));
columns.add(timestampColumn);
columns.add(new UserPropertyColumn<DisplayHistoryEntry>(getString("user"), null, "user", cellItemListener)
.withUserFormatter(userFormatter));
columns
.add(new CellItemListenerPropertyColumn<DisplayHistoryEntry>(getString("history.entryType"), null, "entryType", cellItemListener));
columns.add(new CellItemListenerPropertyColumn<DisplayHistoryEntry>(getString("history.propertyName"), null, "propertyName",
cellItemListener));
columns.add(new CellItemListenerPropertyColumn<DisplayHistoryEntry>(getString("history.newValue"), null, "newValue", cellItemListener) {
@Override
public void populateItem(final Item<ICellPopulator<DisplayHistoryEntry>> item, final String componentId,
final IModel<DisplayHistoryEntry> rowModel)
{
final DisplayHistoryEntry historyEntry = rowModel.getObject();
item.add(new DiffTextPanel(componentId, Model.of(historyEntry.getNewValue()), Model.of(historyEntry.getOldValue())));
cellItemListener.populateItem(item, componentId, rowModel);
}
});
final IDataProvider<DisplayHistoryEntry> dataProvider = new ListDataProvider<DisplayHistoryEntry>(getHistory());
final DataTable<DisplayHistoryEntry, String> dataTable = new DataTable<DisplayHistoryEntry, String>("historyTable", columns,
dataProvider, 100) {
@Override
protected Item<DisplayHistoryEntry> newRowItem(final String id, final int index, final IModel<DisplayHistoryEntry> model)
{
return new OddEvenItem<DisplayHistoryEntry>(id, index, model);
}
@Override
public boolean isVisible()
{
return showHistory;
}
};
final HeadersToolbar<String> headersToolbar = new HeadersToolbar<String>(dataTable, null);
dataTable.addTopToolbar(headersToolbar);
body.add(dataTable);
final Label timeOfCreationLabel = new Label("timeOfCreation", dateTimeFormatter.getFormattedDateTime(data.getCreated()));
timeOfCreationLabel.setRenderBodyOnly(true);
body.add(timeOfCreationLabel);
final Label timeOfLastUpdateLabel = new Label("timeOfLastUpdate", dateTimeFormatter.getFormattedDateTime(data.getLastUpdate()));
timeOfLastUpdateLabel.setRenderBodyOnly(true);
body.add(timeOfLastUpdateLabel);
onPreEdit();
evaluateInitialPageParameters(getPageParameters());
this.editPageSupport = new EditPageSupport<O, D>(this, getBaseDao());
}
protected List<DisplayHistoryEntry> getHistory()
{
if (historyEntries == null) {
historyEntries = getBaseDao().getDisplayHistoryEntries(getData());
}
return historyEntries;
}
/**
* Override this method if some initial data or fields have to be set. onPreEdit will be called on both, on adding new data objects and on
* updating existing data objects. The decision on adding or updating depends on getData().getId() != null.
*/
protected void onPreEdit()
{
}
/**
* Will be called before the data object will be stored. Does nothing at default. Any return value is not yet supported.
*/
public AbstractSecuredBasePage onSaveOrUpdate()
{
// Do nothing at default
return null;
}
/**
* Will be called before the data object will be deleted or marked as deleted. Here you can add validation errors manually. If this method
* returns a resolution then a redirect to this resolution without calling the baseDao methods will done. <br/>
* Here you can do validations with add(Global)Error or manipulate the data object before storing to the database etc.
*/
public AbstractSecuredBasePage onDelete()
{
// Do nothing at default
return null;
}
/**
* Will be called before the data object will be restored (undeleted). Here you can add validation errors manually. If this method returns
* a resolution then a redirect to this resolution without calling the baseDao methods will done. <br/>
* Here you can do validations with add(Global)Error or manipulate the data object before storing to the database etc.
*/
public AbstractSecuredBasePage onUndelete()
{
// Do nothing at default
return null;
}
/**
* Will be called directly after storing the data object (insert, update, delete). If any page is returned then proceed a redirect to this
* given page.
*/
public AbstractSecuredBasePage afterSaveOrUpdate()
{
// Do nothing at default.
return null;
}
/**
* Will be called directly after storing the data object (insert). Any return value is not yet supported.
*/
public AbstractSecuredBasePage afterSave()
{
// Do nothing at default.
return null;
}
/**
* Will be called directly after storing the data object (update).
* @param modificationStatus MINOR or MAJOR, if the object was modified, otherwise NONE. If a not null web page is returned, then the web
* page will be set as response page.
* @see BaseDao#update(ExtendedBaseDO)
*/
public AbstractSecuredBasePage afterUpdate(final ModificationStatus modificationStatus)
{
// Do nothing at default.
return null;
}
/**
* Will be called directly after deleting the data object (delete or update deleted=true). Any return value is not yet supported.
*/
@Override
public WebPage afterDelete()
{
// Do nothing at default.
return null;
}
/**
* Will be called directly after un-deleting the data object (update deleted=false). Any return value is not yet supported.
*/
@Override
public WebPage afterUndelete()
{
// Do nothing at default.
return null;
}
/**
* Will be called by clone button. Sets the id of the form data object to null and deleted to false.
*/
protected void cloneData()
{
final O data = getData();
getLogger().info("Clone of data chosen: " + data);
data.setId(null);
data.setDeleted(false);
}
/**
* If user tried to add a new object and an error was occurred the edit page is shown again and the object id is cleared (set to null).
*/
public void clearIds()
{
getData().setId(null);
}
@Override
public void setResponsePageAndHighlightedRow(final WebPage page)
{
if (getData().getId() != null) {
if (page instanceof AbstractListPage< ? , ? , ? >) {
// Force reload/refresh of calling AbstractListPage, otherwise the data object will not be updated.
((AbstractListPage< ? , ? , ? >) page).setHighlightedRowId(getHighlightedRowId());
((AbstractListPage< ? , ? , ? >) page).refresh();
} else if (returnToPage instanceof TaskTreePage) {
// Force reload/refresh of calling AbstractListPage, otherwise the data object will not be updated.
((TaskTreePage) page).setHighlightedRowId((Integer) getHighlightedRowId());
((TaskTreePage) page).refresh();
} else if (returnToPage instanceof WizardPage) {
((WizardPage) returnToPage).setCreatedObject(getData());
}
}
setResponsePage(page);
}
/**
* Overwrite this, if getData().getId() should not be used.
*/
protected Serializable getHighlightedRowId()
{
return getData().getId();
}
protected void cancel()
{
getLogger().debug("onCancel");
setResponsePage();
}
/**
* User has clicked the save button for storing a new item.
*/
protected void create()
{
this.editPageSupport.create();
}
/**
* User has clicked the update button for updating an existing item.
*/
protected void update()
{
this.editPageSupport.update();
}
/**
* User has clicked the update button for updating an existing item.
*/
protected void updateAndNext()
{
this.editPageSupport.updateAndNext();
}
protected void undelete()
{
this.editPageSupport.undelete();
}
protected void markAsDeleted()
{
this.editPageSupport.markAsDeleted();
}
protected void delete()
{
this.editPageSupport.delete();
}
protected void reset()
{
getLogger().debug("onReset");
// Later: Clearing all fields and restoring data base object.
throw new UnsupportedOperationException("Reset button not supported.");
}
/**
* Sets the list page (declared as annotation) as response or, if given, the returnToPage.
*/
public void setResponsePage()
{
if (this.returnToPage != null) {
setResponsePageAndHighlightedRow(this.returnToPage);
} else {
final EditPage ann = getClass().getAnnotation(EditPage.class);
final Class< ? extends WebPage> redirectPage;
if (ann != null && ann.defaultReturnPage() != null) {
redirectPage = getClass().getAnnotation(EditPage.class).defaultReturnPage();
} else {
redirectPage = WicketUtils.getDefaultPage();
}
final PageParameters params = new PageParameters();
if (getData().getId() != null) {
params.add(AbstractListPage.PARAMETER_HIGHLIGHTED_ROW, getData().getId());
}
setResponsePage(redirectPage, params);
}
}
/**
* @return false, if not overridden.
*/
public boolean isUpdateAndNextSupported()
{
return false;
}
/**
* Convenience method.
* @see AbstractEditForm#getData()
*/
@Override
public O getData()
{
if (form == null || form.getData() == null) {
getLogger().error("Data of form is null. Maybe you have forgotten to call AbstractEditPage.init() in constructor.");
}
return form.getData();
}
/**
* Checks weather the id of the data object is given or not.
* @return true if the user wants to create a new data object or false for an already existing object.
*/
public boolean isNew()
{
if (form == null) {
getLogger().error("Data of form is null. Maybe you have forgotten to call AbstractEditPage.init() in constructor.");
}
return (getData() == null || getData().getId() == null);
}
/**
* Calls getString(key) with key "[i18nPrefix].title.edit" or "[i18nPrefix].title.add" dependent weather the data object is already
* existing or new.
* @see org.projectforge.web.wicket.AbstractUnsecurePage#getTitle()
*/
@Override
protected String getTitle()
{
return getString(getTitleKey(i18nPrefix, isNew()));
}
/**
* @param i18nPrefix
* @param isNew
* @return i18nPrefix + ".title.add" if isNew is true or i18nPrefix + ".title.edit" otherwise.
*/
public static String getTitleKey(final String i18nPrefix, final boolean isNew)
{
if (isNew == true) {
return i18nPrefix + ".title.add";
} else {
return i18nPrefix + ".title.edit";
}
}
/**
* Removes id from the initial parameters set.
* @see org.projectforge.web.wicket.AbstractSecuredPage#getBookmarkableInitialParameters()
*/
@Override
public PageParameters getBookmarkableInitialParameters()
{
if (isNew() == true) {
return new PageParameters();
}
final PageParameters pageParameters = super.getBookmarkableInitialParameters();
pageParameters.remove("id"); // Don't show id if other extended parameters are given.
return pageParameters;
}
/**
* @see org.projectforge.web.wicket.AbstractSecuredPage#getDataObjectForInitialParameters()
*/
@Override
protected Object getDataObjectForInitialParameters()
{
return getData();
}
/**
* @see org.projectforge.web.wicket.AbstractSecuredPage#getTitleKey4BookmarkableInitialParameters()
*/
@Override
public String getTitleKey4BookmarkableInitialParameters()
{
return "bookmark.directPageExtendedLink.editPage";
}
@Override
public boolean isAlreadySubmitted()
{
return alreadySubmitted;
}
@Override
public void setAlreadySubmitted(final boolean alreadySubmitted)
{
this.alreadySubmitted = alreadySubmitted;
}
/**
* @return the form
*/
public F getForm()
{
return form;
}
protected abstract D getBaseDao();
protected abstract Logger getLogger();
protected abstract F newEditForm(AbstractEditPage< ? , ? , ? > parentPage, O data);
}