From: Mathieu Baudier Date: Wed, 14 Sep 2016 16:18:54 +0000 (+0000) Subject: Split CMS and CMS UI X-Git-Tag: argeo-commons-2.1.46~34 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=972528f4de2d00690362c01d3ce843ca9cd10250;p=lgpl%2Fargeo-commons.git Split CMS and CMS UI git-svn-id: https://svn.argeo.org/commons/trunk@9141 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/dep/org.argeo.dep.cms.node/pom.xml b/dep/org.argeo.dep.cms.node/pom.xml index 1c0808965..21c8c4297 100644 --- a/dep/org.argeo.dep.cms.node/pom.xml +++ b/dep/org.argeo.dep.cms.node/pom.xml @@ -27,6 +27,11 @@ org.argeo.cms 2.1.46-SNAPSHOT + + org.argeo.commons + org.argeo.cms.ui + 2.1.46-SNAPSHOT + org.argeo.commons org.argeo.enterprise diff --git a/dep/org.argeo.dep.cms.platform/pom.xml b/dep/org.argeo.dep.cms.platform/pom.xml index 91a354198..25590aa82 100644 --- a/dep/org.argeo.dep.cms.platform/pom.xml +++ b/dep/org.argeo.dep.cms.platform/pom.xml @@ -17,11 +17,6 @@ org.argeo.dep.cms.node 2.1.46-SNAPSHOT - - org.argeo.commons - org.argeo.security.ui - 2.1.46-SNAPSHOT - org.argeo.commons org.argeo.eclipse.ui.workbench diff --git a/org.argeo.cms.ui.workbench.rap/pom.xml b/org.argeo.cms.ui.workbench.rap/pom.xml index 2822051e2..7fcce46ba 100644 --- a/org.argeo.cms.ui.workbench.rap/pom.xml +++ b/org.argeo.cms.ui.workbench.rap/pom.xml @@ -13,12 +13,7 @@ org.argeo.commons - org.argeo.util - 2.1.46-SNAPSHOT - - - org.argeo.commons - org.argeo.security.ui + org.argeo.cms.ui.workbench 2.1.46-SNAPSHOT @@ -26,10 +21,5 @@ org.argeo.eclipse.ui.rap 2.1.46-SNAPSHOT - - org.argeo.commons - org.argeo.cms - 2.1.46-SNAPSHOT - \ No newline at end of file diff --git a/org.argeo.cms.ui.workbench/pom.xml b/org.argeo.cms.ui.workbench/pom.xml index 2dccd6a3f..1a2cd3429 100644 --- a/org.argeo.cms.ui.workbench/pom.xml +++ b/org.argeo.cms.ui.workbench/pom.xml @@ -13,7 +13,7 @@ org.argeo.commons - org.argeo.cms + org.argeo.cms.ui 2.1.46-SNAPSHOT diff --git a/org.argeo.cms.ui/.classpath b/org.argeo.cms.ui/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/org.argeo.cms.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.ui/.project b/org.argeo.cms.ui/.project new file mode 100644 index 000000000..e52eb8ef7 --- /dev/null +++ b/org.argeo.cms.ui/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.ui/bnd.bnd b/org.argeo.cms.ui/bnd.bnd new file mode 100644 index 000000000..e472a2c52 --- /dev/null +++ b/org.argeo.cms.ui/bnd.bnd @@ -0,0 +1,11 @@ +Bundle-Activator: org.argeo.cms.ui.internal.Activator +Bundle-ActivationPolicy: lazy + +Import-Package: org.eclipse.swt,\ +org.eclipse.jface.window,\ +org.eclipse.core.commands,\ +javax.jcr.security,\ +org.argeo.eclipse.ui.dialogs,\ +org.springframework.context,\ +org.springframework.core.io,\ +* \ No newline at end of file diff --git a/org.argeo.cms.ui/build.properties b/org.argeo.cms.ui/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.cms.ui/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.ui/pom.xml b/org.argeo.cms.ui/pom.xml new file mode 100644 index 000000000..90fffa5be --- /dev/null +++ b/org.argeo.cms.ui/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.1.46-SNAPSHOT + .. + + org.argeo.cms.ui + Argeo CMS UI + jar + + + org.argeo.commons + org.argeo.cms + 2.1.46-SNAPSHOT + + + org.argeo.commons + org.argeo.eclipse.ui.rap + 2.1.46-SNAPSHOT + provided + + + \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableLink.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableLink.java new file mode 100644 index 000000000..ece0be36b --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableLink.java @@ -0,0 +1,75 @@ +package org.argeo.cms.forms; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.viewers.EditablePart; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable String that displays a browsable link when read-only */ +public class EditableLink extends EditablePropertyString implements + EditablePart { + private static final long serialVersionUID = 5055000749992803591L; + + private String type; + private String message; + private boolean readOnly; + + public EditableLink(Composite parent, int style, Node node, + String propertyName, String type, String message) + throws RepositoryException { + super(parent, style, node, propertyName, message); + this.message = message; + this.type = type; + + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + if (node.hasProperty(propertyName)) { + this.setStyle(FormStyle.propertyText.style()); + this.setText(node.getProperty(propertyName).getString()); + } else { + this.setStyle(FormStyle.propertyMessage.style()); + this.setText(""); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message); + else if (readOnly) + setLinkValue(lbl, text); + else + // if canEdit() we put only the value with no link + // to avoid glitches of the edition life cycle + lbl.setText(text); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + txt.setMessage(message); + } else + txt.setText(text); + } + } + + private void setLinkValue(Label lbl, String text) { + if (FormStyle.email.style().equals(type)) + lbl.setText(FormUtils.getMailLink(text)); + else if (FormStyle.phone.style().equals(type)) + lbl.setText(FormUtils.getPhoneLink(text)); + else if (FormStyle.website.style().equals(type)) + lbl.setText(FormUtils.getUrlLink(text)); + else if (FormStyle.facebook.style().equals(type) + || FormStyle.instagram.style().equals(type) + || FormStyle.linkedIn.style().equals(type) + || FormStyle.twitter.style().equals(type)) + lbl.setText(FormUtils.getUrlLink(text)); + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableMultiStringProperty.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableMultiStringProperty.java new file mode 100644 index 000000000..fdaa0365c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditableMultiStringProperty.java @@ -0,0 +1,267 @@ +package org.argeo.cms.forms; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Display, add or remove values from a list in a CMS context */ +public class EditableMultiStringProperty extends StyledControl implements + EditablePart { + private static final long serialVersionUID = -7044614381252178595L; + + private String propertyName; + private String message; + // TODO implement the ability to provide a list of legal values + private String[] possibleValues; + private boolean canEdit; + private SelectionListener removeValueSL; + private List values; + + // TODO manage within the CSS + private int rowSpacing = 5; + private int rowMarging = 0; + private int oneValueMargingRight = 5; + private int btnWidth = 16; + private int btnHeight = 16; + private int btnHorizontalIndent = 3; + + public EditableMultiStringProperty(Composite parent, int style, Node node, + String propertyName, List values, String[] possibleValues, + String addValueMsg, SelectionListener removeValueSelectionListener) + throws RepositoryException { + super(parent, style, node, true); + + this.propertyName = propertyName; + this.values = values; + this.possibleValues = possibleValues; + this.message = addValueMsg; + this.canEdit = removeValueSelectionListener != null; + this.removeValueSL = removeValueSelectionListener; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + // Row layout items do not need explicit layout data + protected void setControlLayoutData(Control control) { + } + + /** To be overridden */ + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsUtils.fillWidth()); + } + + @Override + public Control getControl() { + return super.getControl(); + } + + @Override + protected Control createControl(Composite box, String style) { + Composite row = new Composite(box, SWT.NO_FOCUS); + row.setLayoutData(EclipseUiUtils.fillAll()); + + RowLayout rl = new RowLayout(SWT.HORIZONTAL); + rl.wrap = true; + rl.spacing = rowSpacing; + rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging; + row.setLayout(rl); + + if (values != null) { + for (final String value : values) { + if (canEdit) + createRemovableValue(row, SWT.SINGLE, value); + else + createValueLabel(row, SWT.SINGLE, value); + } + } + + if (!canEdit) + return row; + else if (isEditing()) + return createText(row, style); + else + return createLabel(row, style); + } + + /** + * Override to provide specific layout for the existing values, typically + * adding a pound (#) char for tags or anchor info for browsable links. We + * assume the parent composite already has a layout and it is the caller + * responsibility to apply corresponding layout data + */ + protected Label createValueLabel(Composite parent, int style, String value) { + Label label = new Label(parent, style); + label.setText("#" + value); + CmsUtils.markup(label); + CmsUtils.style(label, FormStyle.propertyText.style()); + return label; + } + + private Composite createRemovableValue(Composite parent, int style, + String value) { + Composite valCmp = new Composite(parent, SWT.NO_FOCUS); + GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, + false)); + gl.marginRight = oneValueMargingRight; + valCmp.setLayout(gl); + + createValueLabel(valCmp, SWT.WRAP, value); + + Button deleteBtn = new Button(valCmp, SWT.FLAT); + deleteBtn.setData(FormConstants.LINKED_VALUE, value); + deleteBtn.addSelectionListener(removeValueSL); + CmsUtils.style(deleteBtn, FormStyle.delete.style() + + FormStyle.BUTTON_SUFFIX); + GridData gd = new GridData(); + gd.heightHint = btnHeight; + gd.widthHint = btnWidth; + gd.horizontalIndent = btnHorizontalIndent; + deleteBtn.setLayoutData(gd); + + return valCmp; + } + + protected Text createText(Composite box, String style) { + final Text text = new Text(box, getStyle()); + // The "add new value" text is not meant to change, so we can set it on + // creation + text.setMessage(message); + CmsUtils.style(text, style); + text.setFocus(); + + text.addTraverseListener(new TraverseListener() { + private static final long serialVersionUID = 1L; + + public void keyTraversed(TraverseEvent e) { + if (e.keyCode == SWT.CR) { + addValue(text); + e.doit = false; + } + } + }); + + // The OK button does not work with the focusOut listener + // because focus out is called before the OK button is pressed + + // // we must call layout() now so that the row data can compute the + // height + // // of the other controls. + // text.getParent().layout(); + // int height = text.getSize().y; + // + // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM); + // okBtn.setText("OK"); + // RowData rd = new RowData(SWT.DEFAULT, height - 2); + // okBtn.setLayoutData(rd); + // + // okBtn.addSelectionListener(new SelectionAdapter() { + // private static final long serialVersionUID = 2780819012423622369L; + // + // @Override + // public void widgetSelected(SelectionEvent e) { + // addValue(text); + // } + // }); + + return text; + } + + /** Performs the real addition, overwrite to make further sanity checks */ + protected void addValue(Text text) { + String value = text.getText(); + String errMsg = null; + + if (EclipseUiUtils.isEmpty(value)) + return; + + if (values.contains(value)) + errMsg = "Dupplicated value: " + value + + ", please correct and try again"; + if (errMsg != null) + MessageDialog.openError(this.getShell(), "Addition not allowed", + errMsg); + else { + values.add(value); + Composite newCmp = createRemovableValue(text.getParent(), + SWT.SINGLE, value); + newCmp.moveAbove(text); + text.setText(""); + newCmp.getParent().layout(); + } + } + + protected Label createLabel(Composite box, String style) { + if (canEdit) { + Label lbl = new Label(box, getStyle()); + lbl.setText(message); + CmsUtils.style(lbl, style); + CmsUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + return null; + } + + protected void clear(boolean deep) { + Control child = getControl(); + if (deep) + super.clear(deep); + else { + child.getParent().dispose(); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (canEdit) + lbl.setText(text); + else + lbl.setText(""); + } else if (child instanceof Text) { + Text txt = (Text) child; + txt.setText(text); + } + } + + public synchronized void startEditing() { + getControl().setData(STYLE, FormStyle.propertyText.style()); + super.startEditing(); + } + + public synchronized void stopEditing() { + getControl().setData(STYLE, FormStyle.propertyMessage.style()); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyDate.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyDate.java new file mode 100644 index 000000000..928a28508 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyDate.java @@ -0,0 +1,304 @@ +package org.argeo.cms.forms; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DateTime; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** CMS form part to display and edit a date */ +public class EditablePropertyDate extends StyledControl implements EditablePart { + private static final long serialVersionUID = 2500215515778162468L; + + // Context + private String propertyName; + private String message; + private DateFormat dateFormat; + + // UI Objects + private Text dateTxt; + private Button openCalBtn; + + // TODO manage within the CSS + private int fieldBtnSpacing = 5; + + /** + * + * @param parent + * @param style + * @param node + * @param propertyName + * @param message + * @param dateFormat + * provide a {@link DateFormat} as contract to be able to + * read/write dates as strings + * @throws RepositoryException + */ + public EditablePropertyDate(Composite parent, int style, Node node, + String propertyName, String message, DateFormat dateFormat) + throws RepositoryException { + super(parent, style, node, false); + + this.propertyName = propertyName; + this.message = message; + this.dateFormat = dateFormat; + + if (node.hasProperty(propertyName)) { + this.setStyle(FormStyle.propertyText.style()); + this.setText(dateFormat.format(node.getProperty(propertyName) + .getDate().getTime())); + } else { + this.setStyle(FormStyle.propertyMessage.style()); + this.setText(message); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message); + else + lbl.setText(text); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + } else + txt.setText(text); + } + } + + public synchronized void startEditing() { + // if (dateTxt != null && !dateTxt.isDisposed()) + getControl().setData(STYLE, FormStyle.propertyText.style()); + super.startEditing(); + } + + public synchronized void stopEditing() { + if (EclipseUiUtils.isEmpty(dateTxt.getText())) + getControl().setData(STYLE, FormStyle.propertyMessage.style()); + else + getControl().setData(STYLE, FormStyle.propertyText.style()); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + return createCustomEditableControl(box, style); + } else + return createLabel(box, style); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle() | SWT.WRAP); + lbl.setLayoutData(CmsUtils.fillWidth()); + CmsUtils.style(lbl, style); + CmsUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + private Control createCustomEditableControl(Composite box, String style) { + box.setLayoutData(CmsUtils.fillWidth()); + Composite dateComposite = new Composite(box, SWT.NONE); + GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, + false)); + gl.horizontalSpacing = fieldBtnSpacing; + dateComposite.setLayout(gl); + dateTxt = new Text(dateComposite, SWT.BORDER); + CmsUtils.style(dateTxt, style); + dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT)); + dateTxt.setToolTipText("Enter a date with form \"" + + FormUtils.DEFAULT_SHORT_DATE_FORMAT + + "\" or use the calendar"); + openCalBtn = new Button(dateComposite, SWT.FLAT); + CmsUtils.style(openCalBtn, FormStyle.calendar.style() + + FormStyle.BUTTON_SUFFIX); + GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); + gd.heightHint = 17; + openCalBtn.setLayoutData(gd); + // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN); + + openCalBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 1L; + + public void widgetSelected(SelectionEvent event) { + CalendarPopup popup = new CalendarPopup(dateTxt); + popup.open(); + } + }); + + // dateTxt.addFocusListener(new FocusListener() { + // private static final long serialVersionUID = 1L; + // + // @Override + // public void focusLost(FocusEvent event) { + // String newVal = dateTxt.getText(); + // // Enable reset of the field + // if (FormUtils.notNull(newVal)) + // calendar = null; + // else { + // try { + // Calendar newCal = parseDate(newVal); + // // DateText.this.setText(newCal); + // calendar = newCal; + // } catch (ParseException pe) { + // // Silent. Manage error popup? + // if (calendar != null) + // EditablePropertyDate.this.setText(calendar); + // } + // } + // } + // + // @Override + // public void focusGained(FocusEvent event) { + // } + // }); + return dateTxt; + } + + protected void clear(boolean deep) { + Control child = getControl(); + if (deep || child instanceof Label) + super.clear(deep); + else { + child.getParent().dispose(); + } + } + + /** Enable setting a custom tooltip on the underlying text */ + @Deprecated + public void setToolTipText(String toolTipText) { + dateTxt.setToolTipText(toolTipText); + } + + @Deprecated + /** Enable setting a custom message on the underlying text */ + public void setMessage(String message) { + dateTxt.setMessage(message); + } + + @Deprecated + public void setText(Calendar cal) { + String newValueStr = ""; + if (cal != null) + newValueStr = dateFormat.format(cal.getTime()); + if (!newValueStr.equals(dateTxt.getText())) + dateTxt.setText(newValueStr); + } + + // UTILITIES TO MANAGE THE CALENDAR POPUP + // TODO manage the popup shell in a cleaner way + private class CalendarPopup extends Shell { + private static final long serialVersionUID = 1L; + private DateTime dateTimeCtl; + + public CalendarPopup(Control source) { + super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + populate(); + // Add border and shadow style + CmsUtils.markup(CalendarPopup.this); + CmsUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style()); + pack(); + layout(); + setLocation(source.toDisplay((source.getLocation().x - 2), + (source.getSize().y) + 3)); + + addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + close(); + dispose(); + } + }); + open(); + } + + private void setProperty() { + // Direct set does not seems to work. investigate + // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(), + // dateTimeCtl.getDay(), 12, 0); + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.YEAR, dateTimeCtl.getYear()); + cal.set(Calendar.MONTH, dateTimeCtl.getMonth()); + cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay()); + String dateStr = dateFormat.format(cal.getTime()); + dateTxt.setText(dateStr); + } + + protected void populate() { + setLayout(EclipseUiUtils.noSpaceGridLayout()); + + dateTimeCtl = new DateTime(this, SWT.CALENDAR); + dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll()); + + Calendar calendar = FormUtils.parseDate(dateFormat, + dateTxt.getText()); + + if (calendar != null) + dateTimeCtl.setDate(calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH)); + + dateTimeCtl.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -8414377364434281112L; + + @Override + public void widgetSelected(SelectionEvent e) { + setProperty(); + } + }); + + dateTimeCtl.addMouseListener(new MouseListener() { + private static final long serialVersionUID = 1L; + + @Override + public void mouseUp(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + setProperty(); + close(); + dispose(); + } + }); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyString.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyString.java new file mode 100644 index 000000000..dd3ff29dc --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/EditablePropertyString.java @@ -0,0 +1,80 @@ +package org.argeo.cms.forms; + +import static org.argeo.cms.forms.FormStyle.propertyMessage; +import static org.argeo.cms.forms.FormStyle.propertyText; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.widgets.EditableText; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable String in a CMS context */ +public class EditablePropertyString extends EditableText implements + EditablePart { + private static final long serialVersionUID = 5055000749992803591L; + + private String propertyName; + private String message; + + // encode the '&' character in rap + private final static String AMPERSAND = "&"; + private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)"; + + public EditablePropertyString(Composite parent, int style, Node node, + String propertyName, String message) throws RepositoryException { + super(parent, style, node, true); + + this.propertyName = propertyName; + this.message = message; + + if (node.hasProperty(propertyName)) { + this.setStyle(propertyText.style()); + this.setText(node.getProperty(propertyName).getString()); + } else { + this.setStyle(propertyMessage.style()); + this.setText(message + " "); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message + " "); + else + // TODO enhance this + lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND)); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + txt.setMessage(message + " "); + } else + txt.setText(text.replaceAll("
", "\n")); + } + } + + public synchronized void startEditing() { + getControl().setData(STYLE, propertyText.style()); + super.startEditing(); + } + + public synchronized void stopEditing() { + if (EclipseUiUtils.isEmpty(((Text) getControl()).getText())) + getControl().setData(STYLE, propertyMessage.style()); + else + getControl().setData(STYLE, propertyText.style()); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/FormConstants.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormConstants.java new file mode 100644 index 000000000..18df3e47f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormConstants.java @@ -0,0 +1,7 @@ +package org.argeo.cms.forms; + +/** Constants used in the various CMS Forms */ +public interface FormConstants { + // DATAKEYS + public final static String LINKED_VALUE = "LinkedValue"; +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/FormEditorHeader.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormEditorHeader.java new file mode 100644 index 000000000..c12e2f000 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormEditorHeader.java @@ -0,0 +1,114 @@ +package org.argeo.cms.forms; + +import java.util.Observable; +import java.util.Observer; + +import javax.jcr.Node; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** Add life cycle management abilities to an editable form page */ +public class FormEditorHeader implements SelectionListener, Observer { + private static final long serialVersionUID = 7392898696542484282L; + + // private final Node context; + private final CmsEditable cmsEditable; + private Button publishBtn; + + // Should we provide here the ability to switch from read only to edition + // mode? + // private Button editBtn; + // private boolean readOnly; + + // TODO add information about the current node status, typically if it is + // dirty or not + + private Composite parent; + private Composite display; + private Object layoutData; + + public FormEditorHeader(Composite parent, int style, Node context, + CmsEditable cmsEditable) { + this.cmsEditable = cmsEditable; + this.parent = parent; + // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + // this.context = context; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + refresh(); + } + + public void setLayoutData(Object layoutData) { + this.layoutData = layoutData; + if (display != null && !display.isDisposed()) + display.setLayoutData(layoutData); + } + + protected void refresh() { + if (display != null && !display.isDisposed()) + display.dispose(); + + display = new Composite(parent, SWT.NONE); + display.setLayoutData(layoutData); + + CmsUtils.style(display, FormStyle.header.style()); + display.setBackgroundMode(SWT.INHERIT_FORCE); + + display.setLayout(CmsUtils.noSpaceGridLayout()); + + publishBtn = createSimpleBtn(display, getPublishButtonLabel()); + display.moveAbove(null); + parent.layout(); + } + + private Button createSimpleBtn(Composite parent, String label) { + Button button = new Button(parent, SWT.FLAT | SWT.PUSH); + button.setText(label); + CmsUtils.style(button, FormStyle.header.style()); + button.addSelectionListener(this); + return button; + } + + private String getPublishButtonLabel() { + // Rather check if the current node differs from what has been + // previously committed + // For the time being, we always reach here, the underlying CmsEditable + // is always editing. + if (cmsEditable.isEditing()) + return " Publish "; + else + return " Edit "; + } + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.getSource() == publishBtn) { + // For the time being, the underlying CmsEditable + // is always editing when we reach this point + if (cmsEditable.isEditing()) { + // we always leave the node in a check outed state + cmsEditable.stopEditing(); + cmsEditable.startEditing(); + } else { + cmsEditable.startEditing(); + } + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) { + refresh(); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/FormPageViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormPageViewer.java new file mode 100644 index 000000000..8526fcaca --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormPageViewer.java @@ -0,0 +1,641 @@ +package org.argeo.cms.forms; + +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.CmsNames; +import org.argeo.cms.text.Img; +import org.argeo.cms.ui.internal.text.MarkupValidatorCopy; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.AbstractPageViewer; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.Section; +import org.argeo.cms.viewers.SectionPart; +import org.argeo.cms.widgets.EditableImage; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadEvent; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.fileupload.FileUploadReceiver; +import org.eclipse.rap.rwt.service.ServerPushSession; +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Manage life cycle of a form page that is linked to a given node */ +public class FormPageViewer extends AbstractPageViewer { + private final static Log log = LogFactory.getLog(FormPageViewer.class); + private static final long serialVersionUID = 5277789504209413500L; + + private final Section mainSection; + + // TODO manage within the CSS + private int labelColWidth = 150; + private int rowLayoutHSpacing = 8; + + // Context cached in the viewer + // The reference to translate from text to calendar and reverse + private DateFormat dateFormat = new SimpleDateFormat( + FormUtils.DEFAULT_SHORT_DATE_FORMAT); + private CmsImageManager imageManager; + private FileUploadListener fileUploadListener; + + public FormPageViewer(Section mainSection, int style, + CmsEditable cmsEditable) throws RepositoryException { + super(mainSection, style, cmsEditable); + this.mainSection = mainSection; + + if (getCmsEditable().canEdit()) { + fileUploadListener = new FUL(); + } + } + + @Override + protected void prepare(EditablePart part, Object caretPosition) { + if (part instanceof Img) { + ((Img) part).setFileUploadListener(fileUploadListener); + } + } + + /** To be overridden.Save the edited part. */ + protected void save(EditablePart part) throws RepositoryException { + Node node = null; + if (part instanceof EditableMultiStringProperty) { + EditableMultiStringProperty ept = (EditableMultiStringProperty) part; + // SWT : View + List values = ept.getValues(); + // JCR : Model + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (values.isEmpty()) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + node.setProperty(propName, values.toArray(new String[0])); + } + // => Viewer : Controller + } else if (part instanceof EditablePropertyString) { + EditablePropertyString ept = (EditablePropertyString) part; + // SWT : View + String txt = ((Text) ept.getControl()).getText(); + // JCR : Model + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (EclipseUiUtils.isEmpty(txt)) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + setPropertySilently(node, propName, txt); + // node.setProperty(propName, txt); + } + // node.getSession().save(); + // => Viewer : Controller + } else if (part instanceof EditablePropertyDate) { + EditablePropertyDate ept = (EditablePropertyDate) part; + Calendar cal = FormUtils.parseDate(dateFormat, + ((Text) ept.getControl()).getText()); + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (cal == null) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + node.setProperty(propName, cal); + } + // node.getSession().save(); + // => Viewer : Controller + } + // TODO: make this configurable, sometimes we do not want to save the + // current session at this stage + if (node != null && node.getSession().hasPendingChanges()) { + JcrUtils.updateLastModified(node); + node.getSession().save(); + } + } + + @Override + protected void updateContent(EditablePart part) throws RepositoryException { + if (part instanceof EditableMultiStringProperty) { + EditableMultiStringProperty ept = (EditableMultiStringProperty) part; + // SWT : View + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + List valStrings = new ArrayList(); + if (node.hasProperty(propName)) { + Value[] values = node.getProperty(propName).getValues(); + for (Value val : values) + valStrings.add(val.getString()); + } + ept.setValues(valStrings); + } else if (part instanceof EditablePropertyString) { + // || part instanceof EditableLink + EditablePropertyString ept = (EditablePropertyString) part; + // JCR : Model + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + if (node.hasProperty(propName)) { + String value = node.getProperty(propName).getString(); + ept.setText(value); + } else + ept.setText(""); + // => Viewer : Controller + } else if (part instanceof EditablePropertyDate) { + EditablePropertyDate ept = (EditablePropertyDate) part; + // JCR : Model + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + if (node.hasProperty(propName)) + ept.setText(dateFormat.format(node.getProperty(propName) + .getDate().getTime())); + else + ept.setText(""); + } else if (part instanceof SectionPart) { + SectionPart sectionPart = (SectionPart) part; + Node partNode = sectionPart.getNode(); + // use control AFTER setting style, since it may have been reset + if (part instanceof EditableImage) { + EditableImage editableImage = (EditableImage) part; + imageManager().load(partNode, part.getControl(), + editableImage.getPreferredImageSize()); + } + } + } + + // FILE UPLOAD LISTENER + protected class FUL implements FileUploadListener { + + public FUL() { + } + + public void uploadProgress(FileUploadEvent event) { + // TODO Monitor upload progress + } + + public void uploadFailed(FileUploadEvent event) { + throw new CmsException("Upload failed " + event, + event.getException()); + } + + public void uploadFinished(FileUploadEvent event) { + for (FileDetails file : event.getFileDetails()) { + if (log.isDebugEnabled()) + log.debug("Received: " + file.getFileName()); + } + mainSection.getDisplay().syncExec(new Runnable() { + @Override + public void run() { + saveEdit(); + } + }); + FileUploadHandler uploadHandler = (FileUploadHandler) event + .getSource(); + uploadHandler.dispose(); + } + } + + // FOCUS OUT LISTENER + protected FocusListener createFocusListener() { + return new FocusOutListener(); + } + + private class FocusOutListener implements FocusListener { + private static final long serialVersionUID = -6069205786732354186L; + + @Override + public void focusLost(FocusEvent event) { + saveEdit(); + } + + @Override + public void focusGained(FocusEvent event) { + // does nothing; + } + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (getCmsEditable().canEdit()) { + if (getCmsEditable().isEditing() + && !(getEdited() instanceof Img)) { + if (source == mainSection) + return; + EditablePart part = findDataParent(source); + upload(part); + } else { + getCmsEditable().startEditing(); + } + } + } + } + + @Override + public void mouseDown(MouseEvent e) { + if (getCmsEditable().isEditing()) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + EditablePart composite = findDataParent(source); + Point point = new Point(e.x, e.y); + if (!(composite instanceof Img)) + edit(composite, source.toDisplay(point)); + } else if (e.button == 3) { + // EditablePart composite = findDataParent((Control) e + // .getSource()); + // if (styledTools != null) + // styledTools.show(composite, new Point(e.x, e.y)); + } + } + } + + protected synchronized void upload(EditablePart part) { + if (part instanceof SectionPart) { + if (part instanceof Img) { + if (getEdited() == part) + return; + edit(part, null); + layout(part.getControl()); + } + } + } + } + + @Override + public Control getControl() { + return mainSection; + } + + protected CmsImageManager imageManager() { + if (imageManager == null) + imageManager = CmsUtils.getCmsView().getImageManager(); + return imageManager; + } + + // LOCAL UI HELPERS + protected Section createSectionIfNeeded(Composite body, Node node) + throws RepositoryException { + Section section = null; + if (node != null) { + section = new Section(body, SWT.NO_FOCUS, node); + section.setLayoutData(CmsUtils.fillWidth()); + section.setLayout(CmsUtils.noSpaceGridLayout()); + } + return section; + } + + protected void createSimpleLT(Composite bodyRow, Node node, + String propName, String label, String msg) + throws RepositoryException { + if (getCmsEditable().canEdit() || node.hasProperty(propName)) { + createPropertyLbl(bodyRow, label); + EditablePropertyString eps = new EditablePropertyString(bodyRow, + SWT.WRAP | SWT.LEFT, node, propName, msg); + eps.setMouseListener(getMouseListener()); + eps.setFocusListener(getFocusListener()); + eps.setLayoutData(CmsUtils.fillWidth()); + } + } + + protected void createMultiStringLT(Composite bodyRow, Node node, + String propName, String label, String msg) + throws RepositoryException { + boolean canEdit = getCmsEditable().canEdit(); + if (canEdit || node.hasProperty(propName)) { + createPropertyLbl(bodyRow, label); + + List valueStrings = new ArrayList(); + + if (node.hasProperty(propName)) { + Value[] values = node.getProperty(propName).getValues(); + for (Value value : values) + valueStrings.add(value.getString()); + } + + // TODO use a drop down to display possible values to the end user + EditableMultiStringProperty emsp = new EditableMultiStringProperty( + bodyRow, SWT.SINGLE | SWT.LEAD, node, propName, + valueStrings, new String[] { "Implement this" }, msg, + canEdit ? getRemoveValueSelListener() : null); + addListeners(emsp); + // emsp.setMouseListener(getMouseListener()); + emsp.setStyle(FormStyle.propertyMessage.style()); + emsp.setLayoutData(CmsUtils.fillWidth()); + } + } + + protected Label createPropertyLbl(Composite parent, String value) { + return createPropertyLbl(parent, value, SWT.TOP); + } + + protected Label createPropertyLbl(Composite parent, String value, int vAlign) { + boolean isSmall = CmsUtils.getCmsView().getUxContext().isSmall(); + Label label = new Label(parent, isSmall ? SWT.LEFT : SWT.RIGHT + | SWT.WRAP); + label.setText(value + " "); + CmsUtils.style(label, FormStyle.propertyLabel.style()); + GridData gd = new GridData(isSmall ? SWT.LEFT : SWT.RIGHT, vAlign, + false, false); + gd.widthHint = labelColWidth; + label.setLayoutData(gd); + return label; + } + + protected Label newStyledLabel(Composite parent, String style, String value) { + Label label = new Label(parent, SWT.NONE); + label.setText(value); + CmsUtils.style(label, style); + return label; + } + + protected Composite createRowLayoutComposite(Composite parent) + throws RepositoryException { + Composite bodyRow = new Composite(parent, SWT.NO_FOCUS); + bodyRow.setLayoutData(CmsUtils.fillWidth()); + RowLayout rl = new RowLayout(SWT.WRAP); + rl.type = SWT.HORIZONTAL; + rl.spacing = rowLayoutHSpacing; + rl.marginHeight = rl.marginWidth = 0; + rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0; + bodyRow.setLayout(rl); + return bodyRow; + } + + protected Composite createAddImgComposite(final Section section, + Composite parent, final Node parentNode) throws RepositoryException { + + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayout(new GridLayout()); + + FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, + parentNode, null); + final FileUploadHandler currentUploadHandler = new FileUploadHandler( + receiver); + if (fileUploadListener != null) + currentUploadHandler.addUploadListener(fileUploadListener); + + // Button creation + final FileUpload fileUpload = new FileUpload(body, SWT.BORDER); + fileUpload.setText("Import an image"); + fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, + true)); + fileUpload.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 4869523412991968759L; + + @Override + public void widgetSelected(SelectionEvent e) { + ServerPushSession pushSession = new ServerPushSession(); + pushSession.start(); + String uploadURL = currentUploadHandler.getUploadUrl(); + fileUpload.submit(uploadURL); + } + }); + + return body; + } + + protected class FormFileUploadReceiver extends FileUploadReceiver implements + CmsNames { + + private Node context; + private Section section; + private String name; + + public FormFileUploadReceiver(Section section, Node context, String name) { + this.context = context; + this.section = section; + this.name = name; + } + + @Override + public void receive(InputStream stream, FileDetails details) + throws IOException { + + if (name == null) + name = details.getFileName(); + + // TODO clean image name more carefully + String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "_"); + + try { + imageManager().uploadImage(context, cleanedName, stream); + // TODO clean refresh strategy + section.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + try { + FormPageViewer.this.refresh(section); + section.layout(); + section.getParent().layout(); + } catch (RepositoryException re) { + throw new CmsException("unable to refresh " + + "image section for " + context); + } + } + }); + } catch (RepositoryException re) { + throw new CmsException("unable to upload image " + name + + " at " + context); + } + } + } + + protected void addListeners(StyledControl control) { + control.setMouseListener(getMouseListener()); + control.setFocusListener(getFocusListener()); + } + + protected Img createImgComposite(Composite parent, Node node, + Point preferredSize) throws RepositoryException { + Img img = new Img(parent, SWT.NONE, node, preferredSize) { + private static final long serialVersionUID = 1297900641952417540L; + + @Override + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + + @Override + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + }; + img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + updateContent(img); + addListeners(img); + return img; + } + + protected Composite addDeleteAbility(final Section section, + final Node sessionNode, int topWeight, int rightWeight) { + Composite comp = new Composite(section, SWT.NONE); + comp.setLayoutData(CmsUtils.fillAll()); + comp.setLayout(new FormLayout()); + + // The body to be populated + Composite body = new Composite(comp, SWT.NO_FOCUS); + body.setLayoutData(EclipseUiUtils.fillFormData()); + + if (getCmsEditable().canEdit()) { + // the delete button + Button deleteBtn = new Button(comp, SWT.FLAT); + CmsUtils.style(deleteBtn, FormStyle.deleteOverlay.style()); + FormData formData = new FormData(); + formData.right = new FormAttachment(rightWeight, 0); + formData.top = new FormAttachment(topWeight, 0); + deleteBtn.setLayoutData(formData); + deleteBtn.moveAbove(body); + + deleteBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 4304223543657238462L; + + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + if (MessageDialog.openConfirm(section.getShell(), + "Confirm deletion", + "Are you really you want to remove this?")) { + Session session; + try { + session = sessionNode.getSession(); + Section parSection = section.getParentSection(); + sessionNode.remove(); + session.save(); + refresh(parSection); + layout(parSection); + } catch (RepositoryException re) { + throw new CmsException("Unable to delete " + + sessionNode, re); + } + + } + + } + }); + } + return body; + } + + // LOCAL HELPERS FOR NODE MANAGEMENT + protected Node getOrCreateNode(Node parent, String nodeType, String nodeName) + throws RepositoryException { + Node node = null; + if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) { + node = JcrUtils.mkdirs(parent, nodeName, nodeType); + parent.getSession().save(); + } + + if (getCmsEditable().canEdit() || parent.hasNode(nodeName)) + node = parent.getNode(nodeName); + + return node; + } + + private SelectionListener getRemoveValueSelListener() { + return new SelectionAdapter() { + private static final long serialVersionUID = 9022259089907445195L; + + @Override + public void widgetSelected(SelectionEvent e) { + Object source = e.getSource(); + if (source instanceof Button) { + Button btn = (Button) source; + Object obj = btn.getData(FormConstants.LINKED_VALUE); + EditablePart ep = findDataParent(btn); + if (ep != null && ep instanceof EditableMultiStringProperty) { + EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep; + List values = emsp.getValues(); + if (values.contains(obj)) { + values.remove(values.indexOf(obj)); + emsp.setValues(values); + try { + save(emsp); + // TODO workaround to force refresh + edit(emsp, 0); + cancelEdit(); + } catch (RepositoryException e1) { + throw new CmsException( + "Unable to remove value " + obj, e1); + } + layout(emsp); + } + } + } + } + }; + } + + protected void setPropertySilently(Node node, String propName, String value) + throws RepositoryException { + try { + // TODO Clean this: + // Format strings to replace \n + value = value.replaceAll("\n", "
"); + // Do not make the update if validation fails + try { + MarkupValidatorCopy.getInstance().validate(value); + } catch (Exception e) { + log.warn("Cannot set [" + value + "] on prop " + propName + + "of " + node + ", String cannot be validated - " + + e.getMessage()); + return; + } + // TODO check if the newly created property is of the correct type, + // otherwise the property will be silently created with a STRING + // property type. + node.setProperty(propName, value); + } catch (ValueFormatException vfe) { + log.warn("Cannot set [" + value + "] on prop " + propName + "of " + + node + " - " + vfe.getMessage()); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/FormStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormStyle.java new file mode 100644 index 000000000..1b9880d92 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormStyle.java @@ -0,0 +1,26 @@ +package org.argeo.cms.forms; + +/** Syles used */ +public enum FormStyle { + // Main + form, title, + // main part + header, headerBtn, headerCombo, section, sectionHeader, + // Property fields + propertyLabel, propertyText, propertyMessage, + // Date + popupCalendar, + // Buttons + starred, unstarred, starOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete, + // Contacts + email, address, phone, website, + // Social Media + facebook, twitter, linkedIn, instagram; + + public String style() { + return form.name() + '_' + name(); + } + + // TODO clean button style management + public final static String BUTTON_SUFFIX = "_btn"; +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/forms/FormUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormUtils.java new file mode 100644 index 000000000..f8d08b08d --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/forms/FormUtils.java @@ -0,0 +1,197 @@ +package org.argeo.cms.forms; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsView; +import org.argeo.cms.util.CmsUtils; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Utilitary methods to ease implementation of CMS forms */ +public class FormUtils { + private final static Log log = LogFactory.getLog(FormUtils.class); + + public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy"; + + /** Best effort to convert a String to a calendar. Fails silently */ + public static Calendar parseDate(DateFormat dateFormat, String calStr) { + Calendar cal = null; + if (EclipseUiUtils.notEmpty(calStr)) { + try { + Date date = dateFormat.parse(calStr); + cal = new GregorianCalendar(); + cal.setTime(date); + } catch (ParseException pe) { + // Silent + log.warn("Unable to parse date: " + calStr + " - msg: " + + pe.getMessage()); + } + } + return cal; + } + + /** Add a double click listener on tables that display a JCR node list */ + public static void addCanonicalDoubleClickListener(final TableViewer v) { + v.addDoubleClickListener(new IDoubleClickListener() { + + @Override + public void doubleClick(DoubleClickEvent event) { + CmsView cmsView = CmsUtils.getCmsView(); + Node node = (Node) ((IStructuredSelection) event.getSelection()) + .getFirstElement(); + try { + cmsView.navigateTo(node.getPath()); + } catch (RepositoryException e) { + throw new CmsException("Unable to get path for node " + + node + " before calling navigateTo(path)", e); + } + } + }); + } + + // MANAGE ERROR DECORATION + + public static ControlDecoration addDecoration(final Text text) { + final ControlDecoration dynDecoration = new ControlDecoration(text, + SWT.LEFT); + Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR); + dynDecoration.setImage(icon); + dynDecoration.setMarginWidth(3); + dynDecoration.hide(); + return dynDecoration; + } + + public static void refreshDecoration(Text text, ControlDecoration deco, + boolean isValid, boolean clean) { + if (isValid || clean) { + text.setBackground(null); + deco.hide(); + } else { + text.setBackground(new Color(text.getDisplay(), 250, 200, 150)); + deco.show(); + } + } + + public static Image getDecorationImage(String image) { + FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault(); + return registry.getFieldDecoration(image).getImage(); + } + + public static void addCompulsoryDecoration(Label label) { + final ControlDecoration dynDecoration = new ControlDecoration(label, + SWT.RIGHT | SWT.TOP); + Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED); + dynDecoration.setImage(icon); + dynDecoration.setMarginWidth(3); + } + + // TODO the read only generation of read only links for various contact type + // should be factorised in the cms Utils. + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able phone number + */ + public static String getPhoneLink(String value) { + return getPhoneLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able phone number + * + * @param value + * @param label + * a potentially distinct label + * @return + */ + public static String getPhoneLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + builder.append("").append(label) + .append(""); + return builder.toString(); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able mail + */ + public static String getMailLink(String value) { + return getMailLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able mail + * + * @param value + * @param label + * a potentially distinct label + * @return + */ + public static String getMailLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + value = replaceAmpersand(value); + builder.append("").append(label).append(""); + return builder.toString(); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able link + */ + public static String getUrlLink(String value) { + return getUrlLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able link + */ + public static String getUrlLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + value = replaceAmpersand(value); + label = replaceAmpersand(label); + if (!(value.startsWith("http://") || value.startsWith("https://"))) + value = "http://" + value; + builder.append("" + label + ""); + return builder.toString(); + } + + private static String AMPERSAND = "&"; + + /** + * Cleans a String by replacing any '&' by its HTML encoding '&' to + * avoid SAXParseException while rendering HTML with RWT + */ + public static String replaceAmpersand(String value) { + value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND); + return value; + } + + // Prevents instantiation + private FormUtils() { + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java new file mode 100644 index 000000000..2494d90c3 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java @@ -0,0 +1,43 @@ +package org.argeo.cms.maintenance; + +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +abstract class AbstractOsgiComposite extends Composite { + private static final long serialVersionUID = -4097415973477517137L; + protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + protected final Log log = LogFactory.getLog(getClass()); + + public AbstractOsgiComposite(Composite parent, int style) { + super(parent, style); + parent.setLayout(CmsUtils.noSpaceGridLayout()); + setLayout(CmsUtils.noSpaceGridLayout()); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + initUi(style); + } + + protected abstract void initUi(int style); + + protected T getService(Class clazz) { + return bc.getService(bc.getServiceReference(clazz)); + } + + protected Collection> getServiceReferences(Class clazz, String filter) { + try { + return bc.getServiceReferences(clazz, filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Filter " + filter + " is invalid", e); + } + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/Browse.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/Browse.java new file mode 100644 index 000000000..7dafd72c4 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/Browse.java @@ -0,0 +1,616 @@ +package org.argeo.cms.maintenance; + +import static javax.jcr.Node.JCR_CONTENT; +import static org.eclipse.swt.SWT.RIGHT; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.LinkedHashMap; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsTypes; +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.text.Img; +import org.argeo.cms.util.CmsLink; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.widgets.EditableImage; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; + +public class Browse implements CmsUiProvider { + + // Some local constants to experiment. should be cleaned + private final static String BROWSE_PREFIX = "browse#"; + private final static int THUMBNAIL_WIDTH = 400; + private final static int COLUMN_WIDTH = 160; + private DateFormat timeFormatter = new SimpleDateFormat( + "dd-MM-yyyy', 'HH:mm"); + + // keep a cache of the opened nodes + // Key is the path + private LinkedHashMap browserCols = new LinkedHashMap(); + private Composite nodeDisplayParent; + private Composite colViewer; + private ScrolledComposite scrolledCmp; + private Text parentPathTxt; + private Text filterTxt; + private Node currEdited; + + private String initialPath; + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + if (context == null) + // return null; + throw new CmsException("Context cannot be null"); + GridLayout layout = CmsUtils.noSpaceGridLayout(); + layout.numColumns = 2; + parent.setLayout(layout); + + // Left + Composite leftCmp = new Composite(parent, SWT.NO_FOCUS); + leftCmp.setLayoutData(CmsUtils.fillAll()); + createBrowserPart(leftCmp, context); + + // Right + nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); + GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true); + gd.widthHint = THUMBNAIL_WIDTH; + nodeDisplayParent.setLayoutData(gd); + createNodeView(nodeDisplayParent, context); + + // INIT + setEdited(context); + initialPath = context.getPath(); + + // Workaround we don't yet manage the delete to display parent of the + // initial context node + + return null; + } + + private void createBrowserPart(Composite parent, Node context) + throws RepositoryException { + GridLayout layout = CmsUtils.noSpaceGridLayout(); + parent.setLayout(layout); + Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); + filterCmp.setLayoutData(CmsUtils.fillWidth()); + + // top filter + addFilterPanel(filterCmp); + + // scrolled composite + scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER + | SWT.NO_FOCUS); + scrolledCmp.setLayoutData(CmsUtils.fillAll()); + scrolledCmp.setExpandVertical(true); + scrolledCmp.setExpandHorizontal(true); + scrolledCmp.setShowFocusedControl(true); + + colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS); + scrolledCmp.setContent(colViewer); + scrolledCmp.addControlListener(new ControlAdapter() { + private static final long serialVersionUID = 6589392045145698201L; + + @Override + public void controlResized(ControlEvent e) { + Rectangle r = scrolledCmp.getClientArea(); + scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, + r.height)); + } + }); + initExplorer(colViewer, context); + } + + private Control initExplorer(Composite parent, Node context) + throws RepositoryException { + parent.setLayout(CmsUtils.noSpaceGridLayout()); + createBrowserColumn(parent, context); + return null; + } + + private Control createBrowserColumn(Composite parent, Node context) + throws RepositoryException { + // TODO style is not correctly managed. + FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable( + parent, SWT.BORDER | SWT.NO_FOCUS, context); + // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style()); + table.filterList("*"); + table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); + browserCols.put(context.getPath(), table); + return null; + } + + public void addFilterPanel(Composite parent) { + + parent.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(2, false))); + + // Text Area for the filter + parentPathTxt = new Text(parent, SWT.NO_FOCUS); + parentPathTxt.setEditable(false); + filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); + filterTxt.setMessage("Filter current list"); + filterTxt.setLayoutData(CmsUtils.fillWidth()); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7709303319740056286L; + + public void modifyText(ModifyEvent event) { + modifyFilter(false); + } + }); + + filterTxt.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -4523394262771183968L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0; + // boolean altPressed = (e.stateMask & SWT.ALT) != 0; + FilterEntitiesVirtualTable currTable = null; + if (currEdited != null) { + FilterEntitiesVirtualTable table = browserCols + .get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + currTable = table; + } + + try { + if (e.keyCode == SWT.ARROW_DOWN) + currTable.setFocus(); + else if (e.keyCode == SWT.BS) { + if (filterTxt.getText().equals("") + && !(getPath(currEdited).equals("/") || getPath( + currEdited).equals(initialPath))) { + setEdited(currEdited.getParent()); + e.doit = false; + filterTxt.setFocus(); + } + } else if (e.keyCode == SWT.TAB && !shiftPressed) { + if (currEdited.getNodes(filterTxt.getText() + "*") + .getSize() == 1) { + setEdited(currEdited.getNodes( + filterTxt.getText() + "*").nextNode()); + } + filterTxt.setFocus(); + e.doit = false; + } + } catch (RepositoryException e1) { + throw new CmsException( + "Unexpected error in key management for " + + currEdited + "with filter " + + filterTxt.getText(), e1); + } + + } + }); + } + + private void setEdited(Node node) { + try { + currEdited = node; + CmsUtils.clear(nodeDisplayParent); + createNodeView(nodeDisplayParent, currEdited); + nodeDisplayParent.layout(); + refreshFilters(node); + refreshBrowser(node); + } catch (RepositoryException re) { + throw new CmsException("Unable to update browser for " + node, re); + } + } + + private void refreshFilters(Node node) throws RepositoryException { + String currNodePath = node.getPath(); + parentPathTxt.setText(currNodePath); + filterTxt.setText(""); + filterTxt.getParent().layout(); + } + + private void refreshBrowser(Node node) throws RepositoryException { + + // Retrieve + String currNodePath = node.getPath(); + String currParPath = ""; + if (!"/".equals(currNodePath)) + currParPath = JcrUtils.parentPath(currNodePath); + if ("".equals(currParPath)) + currParPath = "/"; + + + + + Object[][] colMatrix = new Object[browserCols.size()][2]; + + int i = 0, j = -1, k = -1; + for (String path : browserCols.keySet()) { + colMatrix[i][0] = path; + colMatrix[i][1] = browserCols.get(path); + if (j >= 0 && k < 0 && !currNodePath.equals("/")) { + boolean leaveOpened = path.startsWith(currNodePath); + + // workaround for same name siblings + // fix me weird side effect when we go left or click on anb + // already selected, unfocused node + if (leaveOpened + && (path.lastIndexOf("/") == 0 + && currNodePath.lastIndexOf("/") == 0 || JcrUtils + .parentPath(path).equals( + JcrUtils.parentPath(currNodePath)))) + leaveOpened = JcrUtils.lastPathElement(path).equals( + JcrUtils.lastPathElement(currNodePath)); + + if (!leaveOpened) + k = i; + } + if (currParPath.equals(path)) + j = i; + i++; + } + + if (j >= 0 && k >= 0) + // remove useless cols + for (int l = i - 1; l >= k; l--) { + browserCols.remove(colMatrix[l][0]); + ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); + } + + // Remove disposed columns + // TODO investigate and fix the mechanism that leave them there after + // disposal + if (browserCols.containsKey(currNodePath)) { + FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath); + if (currCol.isDisposed()) + browserCols.remove(currNodePath); + } + + if (!browserCols.containsKey(currNodePath)) + createBrowserColumn(colViewer, node); + + colViewer.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout( + browserCols.size(), false))); + // colViewer.pack(); + colViewer.layout(); + // also resize the scrolled composite + scrolledCmp.layout(); + scrolledCmp.getShowFocusedControl(); + // colViewer.getParent().layout(); + // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) { + // } else { + // } + } + + private void modifyFilter(boolean fromOutside) { + if (!fromOutside) + if (currEdited != null) { + String filter = filterTxt.getText() + "*"; + FilterEntitiesVirtualTable table = browserCols + .get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + table.filterList(filter); + } + + } + + private String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get path for node " + node, e); + } + } + + private Point imageWidth = new Point(250, 0); + + /** + * Recreates the content of the box that displays information about the + * current selected node. + */ + private Control createNodeView(Composite parent, Node context) + throws RepositoryException { + + parent.setLayout(new GridLayout(2, false)); + + if (isImg(context)) { + EditableImage image = new Img(parent, RIGHT, context, imageWidth); + image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, + false, 2, 1)); + } + + // Name and primary type + Label contextL = new Label(parent, SWT.NONE); + contextL.setData(RWT.MARKUP_ENABLED, true); + contextL.setText("" + context.getName() + ""); + new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() + .getName()); + + // Children + for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { + Node child = nIt.nextNode(); + new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()) + .createUi(parent, context); + new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() + .getName()); + } + + // Properties + for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { + Property property = pIt.nextProperty(); + Label label = new Label(parent, SWT.NONE); + label.setText(property.getName()); + label.setToolTipText(JcrUtils + .getPropertyDefinitionAsString(property)); + new Label(parent, SWT.NONE).setText(getPropAsString(property)); + } + + return null; + } + + private boolean isImg(Node node) throws RepositoryException { + return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE); + } + + private String getPropAsString(Property property) + throws RepositoryException { + String result = ""; + if (property.isMultiple()) { + result = getMultiAsString(property, ", "); + } else { + Value value = property.getValue(); + if (value.getType() == PropertyType.BINARY) + result = ""; + else if (value.getType() == PropertyType.DATE) + result = timeFormatter.format(value.getDate().getTime()); + else + result = value.getString(); + } + return result; + } + + private String getMultiAsString(Property property, String separator) + throws RepositoryException { + if (separator == null) + separator = "; "; + Value[] values = property.getValues(); + StringBuilder builder = new StringBuilder(); + for (Value val : values) { + String currStr = val.getString(); + if (!"".equals(currStr.trim())) + builder.append(currStr).append(separator); + } + if (builder.lastIndexOf(separator) >= 0) + return builder.substring(0, builder.length() - separator.length()); + else + return builder.toString(); + } + + /** Almost canonical implementation of a table that display entities */ + private class FilterEntitiesVirtualTable extends Composite { + private static final long serialVersionUID = 8798147431706283824L; + + // Context + private Node context; + + // UI Objects + private TableViewer entityViewer; + + // enable management of multiple columns + Node getNode() { + return context; + } + + @Override + public boolean setFocus() { + if (entityViewer.getTable().isDisposed()) + return false; + if (entityViewer.getSelection().isEmpty()) { + Object first = entityViewer.getElementAt(0); + if (first != null) { + entityViewer.setSelection(new StructuredSelection(first), + true); + } + } + return entityViewer.getTable().setFocus(); + } + + void filterList(String filter) { + try { + NodeIterator nit = context.getNodes(filter); + refreshFilteredList(nit); + } catch (RepositoryException e) { + throw new CmsException("Unable to filter " + getNode() + + " children with filter " + filter, e); + } + + } + + public FilterEntitiesVirtualTable(Composite parent, int style, + Node context) { + super(parent, SWT.NO_FOCUS); + this.context = context; + populate(); + } + + protected void populate() { + Composite parent = this; + GridLayout layout = CmsUtils.noSpaceGridLayout(); + + this.setLayout(layout); + createTableViewer(parent); + } + + private void createTableViewer(final Composite parent) { + // the list + // We must limit the size of the table otherwise the full list is + // loaded + // before the layout happens + Composite listCmp = new Composite(parent, SWT.NO_FOCUS); + GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); + gd.widthHint = COLUMN_WIDTH; + listCmp.setLayoutData(gd); + listCmp.setLayout(CmsUtils.noSpaceGridLayout()); + + entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); + Table table = entityViewer.getTable(); + + table.setLayoutData(CmsUtils.fillAll()); + table.setLinesVisible(true); + table.setHeaderVisible(false); + table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + + CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + + // first column + TableViewerColumn column = new TableViewerColumn(entityViewer, + SWT.NONE); + TableColumn tcol = column.getColumn(); + tcol.setWidth(COLUMN_WIDTH); + tcol.setResizable(true); + column.setLabelProvider(new SimpleNameLP()); + + entityViewer.setContentProvider(new MyLazyCP(entityViewer)); + entityViewer + .addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) entityViewer + .getSelection(); + if (selection.isEmpty()) + return; + else + setEdited((Node) selection.getFirstElement()); + + } + }); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -330694313896036230L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + + IStructuredSelection selection = (IStructuredSelection) entityViewer + .getSelection(); + Node selected = null; + if (!selection.isEmpty()) + selected = ((Node) selection.getFirstElement()); + try { + if (e.keyCode == SWT.ARROW_RIGHT) { + if (selected != null) { + setEdited(selected); + browserCols.get(selected.getPath()).setFocus(); + } + } else if (e.keyCode == SWT.ARROW_LEFT) { + try { + selected = getNode().getParent(); + String newPath = selected.getPath(); // getNode().getParent() + setEdited(selected); + if (browserCols.containsKey(newPath)) + browserCols.get(newPath).setFocus(); + } catch (ItemNotFoundException ie) { + // root silent + } + } + } catch (RepositoryException ie) { + throw new CmsException("Error while managing arrow " + + "events in the browser for " + selected, ie); + } + } + }); + } + + private class MyLazyCP implements ILazyContentProvider { + private static final long serialVersionUID = 1L; + private TableViewer viewer; + private Object[] elements; + + public MyLazyCP(TableViewer viewer) { + this.viewer = viewer; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, + Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + public void updateElement(int index) { + viewer.replace(elements[index], index); + } + } + + protected void refreshFilteredList(NodeIterator children) { + Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); + entityViewer.setInput(rows); + entityViewer.setItemCount(rows.length); + entityViewer.refresh(); + } + + public class SimpleNameLP extends ColumnLabelProvider { + private static final long serialVersionUID = 2465059387875338553L; + + @Override + public String getText(Object element) { + if (element instanceof Node) { + Node curr = ((Node) element); + try { + return curr.getName(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get name for" + + curr); + } + } + return super.getText(element); + } + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java new file mode 100644 index 000000000..f4f3079b6 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java @@ -0,0 +1,48 @@ +package org.argeo.cms.maintenance; + +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; +import org.osgi.service.useradmin.UserAdmin; + +class ConnectivityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public ConnectivityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + StringBuffer text = new StringBuffer(); + text.append("Provided Servers
"); + + ServiceReference userAdminRef = bc.getServiceReference(HttpService.class); + if (userAdminRef != null) { + // FIXME use constants + Object httpPort = userAdminRef.getProperty("http.port"); + Object httpsPort = userAdminRef.getProperty("https.port"); + if (httpPort != null) + text.append("http ").append(httpPort).append("
"); + if (httpsPort != null) + text.append("https ").append(httpsPort).append("
"); + + } + + text.append("
"); + text.append("Referenced Servers
"); + + Label label = new Label(this, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DataDeploymentUi.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DataDeploymentUi.java new file mode 100644 index 000000000..efcbaa991 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DataDeploymentUi.java @@ -0,0 +1,139 @@ +package org.argeo.cms.maintenance; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; + +import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.argeo.cms.util.CmsUtils; +import org.argeo.jcr.ArgeoJcrConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.ServiceReference; + +class DataDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public DataDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { +// try { +// ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); +// Configuration[] confs = confAdmin.listConfigurations( +// "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); +// if (confs == null || confs.length == 0) { +// Group buttonGroup = new Group(parent, SWT.NONE); +// buttonGroup.setText("Repository Type"); +// buttonGroup.setLayout(new GridLayout(2, true)); +// buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); +// +// SelectionListener selectionListener = new SelectionAdapter() { +// private static final long serialVersionUID = 6247064348421088092L; +// +// public void widgetSelected(SelectionEvent event) { +// Button radio = (Button) event.widget; +// if (!radio.getSelection()) +// return; +// log.debug(event); +// JackrabbitType nodeType = (JackrabbitType) radio.getData(); +// if (log.isDebugEnabled()) +// log.debug(" selected = " + nodeType.name()); +// }; +// }; +// +// for (JackrabbitType nodeType : JackrabbitType.values()) { +// Button radio = new Button(buttonGroup, SWT.RADIO); +// radio.setText(nodeType.name()); +// radio.setData(nodeType); +// if (nodeType.equals(JackrabbitType.localfs)) +// radio.setSelection(true); +// radio.addSelectionListener(selectionListener); +// } +// +// } else if (confs.length == 1) { +// +// } else { +// throw new CmsException("Multiple repos not yet supported"); +// } +// } catch (Exception e) { +// throw new CmsException("Cannot initialize UI", e); +// } + + } + + private void initCurrentUi(Composite parent) { + parent.setLayout(new GridLayout()); + Collection> contexts = getServiceReferences(RepositoryContext.class, + "(" + ArgeoJcrConstants.JCR_REPOSITORY_ALIAS + "=*)"); + StringBuffer text = new StringBuffer(); + text.append("Jackrabbit Repositories
"); + for (ServiceReference sr : contexts) { + RepositoryContext repositoryContext = bc.getService(sr); + String alias = sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS).toString(); + String rootNodeId = repositoryContext.getRootNodeId().toString(); + RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig(); + Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath(); + // TODO check data store + + text.append("" + alias + "
"); + text.append("rootNodeId: " + rootNodeId + "
"); + try { + FileStore fileStore = Files.getFileStore(repoHomePath); + text.append("partition: " + fileStore.toString() + "
"); + text.append( + percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); + } catch (IOException e) { + log.error("Cannot check fileStore for " + repoHomePath, e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsUtils.markup(label); + label.setText("" + text.toString() + ""); + } + + private String humanReadable(long bytes) { + long mb = bytes / (1024 * 1024); + return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB"; + } + + private String percentUsed(FileStore fs) throws IOException { + long used = fs.getTotalSpace() - fs.getUnallocatedSpace(); + long percent = used * 100 / fs.getTotalSpace(); + if (log.isTraceEnabled()) { + // output identical to `df -B 1`) + log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace()); + } + String span; + if (percent < 80) + span = ""; + else if (percent < 95) + span = ""; + else + span = ""; + return span + percent + "%"; + } + + protected boolean isDeployed() { + return bc.getServiceReference(RepositoryContext.class) != null; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java new file mode 100644 index 000000000..e21974c35 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java @@ -0,0 +1,94 @@ +package org.argeo.cms.maintenance; + +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.cms.util.CmsUtils; +import org.argeo.node.NodeConstants; +import org.argeo.node.NodeDeployment; +import org.argeo.node.NodeState; +import org.eclipse.rap.rwt.application.AbstractEntryPoint; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +class DeploymentEntryPoint extends AbstractEntryPoint { + private static final long serialVersionUID = -881152502968982437L; + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + @Override + protected void createContents(Composite parent) { + // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + if (isDesktop()) { + parent.setLayout(new GridLayout(2, true)); + } else { + // TODO add scrolling + parent.setLayout(new GridLayout(1, true)); + } + + initHighLevelSummary(parent); + + Group securityGroup = createHighLevelGroup(parent, "Security"); + securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new SecurityDeploymentUi(securityGroup, SWT.NONE); + + Group dataGroup = createHighLevelGroup(parent, "Data"); + dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new DataDeploymentUi(dataGroup, SWT.NONE); + + Group logGroup = createHighLevelGroup(parent, "Notifications"); + logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + new LogDeploymentUi(logGroup, SWT.NONE); + + Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); + new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); + connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + + } + + private void initHighLevelSummary(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); + if (isDesktop()) + gridData.horizontalSpan = 3; + composite.setLayoutData(gridData); + composite.setLayout(new FillLayout()); + + ServiceReference nodeStateRef = bc.getServiceReference(NodeState.class); + if (nodeStateRef == null) + throw new IllegalStateException("No CMS state available"); + NodeState nodeState = bc.getService(nodeStateRef); + ServiceReference nodeDeploymentRef = bc.getServiceReference(NodeDeployment.class); + Label label = new Label(composite, SWT.WRAP); + CmsUtils.markup(label); + if (nodeDeploymentRef == null) { + label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, please configure below."); + } else { + Object stateUuid = nodeStateRef.getProperty(NodeConstants.CN); + NodeDeployment nodeDeployment = bc.getService(nodeDeploymentRef); + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(nodeDeployment.getAvailableSince()); + calendar.setTimeZone(TimeZone.getDefault()); + label.setText("[" + "" + nodeState.getHostname() + "]# " + "Deployment state " + stateUuid + + ", available since " + calendar.getTime() + ""); + } + } + + private static Group createHighLevelGroup(Composite parent, String text) { + Group group = new Group(parent, SWT.NONE); + group.setText(text); + CmsUtils.markup(group); + return group; + } + + private boolean isDesktop() { + return true; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/LogDeploymentUi.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/LogDeploymentUi.java new file mode 100644 index 000000000..8fb96437c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/LogDeploymentUi.java @@ -0,0 +1,74 @@ +package org.argeo.cms.maintenance; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.LogReaderService; + +class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { + private static final long serialVersionUID = 590221539553514693L; + + private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); + + private Display display; + private Text logDisplay; + + public LogDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + LogReaderService logReader = getService(LogReaderService.class); + // FIXME use server push + // logReader.addLogListener(this); + this.display = getDisplay(); + this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); + logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + CmsUtils.markup(logDisplay); + @SuppressWarnings("unchecked") + Enumeration logEntries = (Enumeration) logReader.getLog(); + while (logEntries.hasMoreElements()) + logDisplay.append(printEntry(logEntries.nextElement())); + } + + private String printEntry(LogEntry entry) { + StringBuilder sb = new StringBuilder(); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); + calendar.setTimeInMillis(entry.getTime()); + sb.append(dateFormat.format(calendar.getTime())).append(' '); + sb.append(entry.getMessage()); + sb.append('\n'); + return sb.toString(); + } + + @Override + public void logged(LogEntry entry) { + if (display.isDisposed()) + return; + display.asyncExec(() -> { + if (logDisplay.isDisposed()) + return; + logDisplay.append(printEntry(entry)); + }); + display.wake(); + } + + // @Override + // public void dispose() { + // super.dispose(); + // getService(LogReaderService.class).removeLogListener(this); + // } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceStyles.java new file mode 100644 index 000000000..fef25d7ce --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceStyles.java @@ -0,0 +1,10 @@ +package org.argeo.cms.maintenance; + +/** Specific styles used by the various maintenance pages . */ +public interface MaintenanceStyles { + // General + public final static String PREFIX = "maintenance_"; + + // Browser + public final static String BROWSER_COLUMN = "browser_column"; + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceUi.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceUi.java new file mode 100644 index 000000000..538379f06 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/MaintenanceUi.java @@ -0,0 +1,13 @@ +package org.argeo.cms.maintenance; + +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; + +public class MaintenanceUi implements ApplicationConfiguration { + + @Override + public void configure(Application application) { + application.addEntryPoint("/status", DeploymentEntryPoint.class, null); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/NonAdminPage.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/NonAdminPage.java new file mode 100644 index 000000000..b27d50659 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/NonAdminPage.java @@ -0,0 +1,30 @@ +package org.argeo.cms.maintenance; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class NonAdminPage implements CmsUiProvider{ + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayoutData(CmsUtils.fillAll()); + body.setLayout(new GridLayout()); + Label label = new Label(body, SWT.NONE); + label.setText("You should be an admin to perform maintenance operations. " + + "Are you sure you are logged in?"); + label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + return null; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java new file mode 100644 index 000000000..9fcdaf980 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java @@ -0,0 +1,85 @@ +package org.argeo.cms.maintenance; + +import java.net.URI; + +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +class SecurityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public SecurityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { + new Label(parent, SWT.NONE).setText("Security is not configured"); + } + + private void initCurrentUi(Composite parent) { + ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); + UserAdmin userAdmin = bc.getService(userAdminRef); + StringBuffer text = new StringBuffer(); + text.append("Domains
"); + domains: for (String key : userAdminRef.getPropertyKeys()) { + if (!key.startsWith("/")) + continue domains; + URI uri; + try { + uri = new URI(key); + } catch (Exception e) { + // ignore non URI keys + continue domains; + } + + String rootDn = uri.getPath().substring(1, uri.getPath().length()); + // FIXME make reading query options more robust, using utils + boolean readOnly = uri.getQuery().equals("readOnly=true"); + if (readOnly) + text.append(""); + else + text.append(""); + + text.append(rootDn); + text.append("
"); + try { + Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")"); + long userCount = 0; + long groupCount = 0; + for (Role role : roles) { + if (role.getType() == Role.USER) + userCount++; + else + groupCount++; + } + text.append(" " + userCount + " users, " + groupCount +" groups.
"); + } catch (InvalidSyntaxException e) { + log.error("Invalid syntax", e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/CustomTextEditor.java b/org.argeo.cms.ui/src/org/argeo/cms/text/CustomTextEditor.java new file mode 100644 index 000000000..c7491e02c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/CustomTextEditor.java @@ -0,0 +1,35 @@ +package org.argeo.cms.text; + +import static org.argeo.cms.util.CmsUtils.fillWidth; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.ui.internal.text.AbstractTextViewer; +import org.argeo.cms.viewers.Section; +import org.eclipse.swt.widgets.Composite; + +/** + * Manages hardcoded sections as an arbitrary hierarchy under the main section, + * which contains no text and no title. + */ +public class CustomTextEditor extends AbstractTextViewer { + private static final long serialVersionUID = 5277789504209413500L; + + public CustomTextEditor(Composite parent, int style, Node textNode, + CmsEditable cmsEditable) throws RepositoryException { + this(new Section(parent, style, textNode), style, cmsEditable); + } + + public CustomTextEditor(Section mainSection, int style, + CmsEditable cmsEditable) throws RepositoryException { + super(mainSection, style, cmsEditable); + mainSection.setLayoutData(fillWidth()); + } + + @Override + public Section getMainSection() { + return super.getMainSection(); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java b/org.argeo.cms.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java new file mode 100644 index 000000000..79f6ede2c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/IdentityTextInterpreter.java @@ -0,0 +1,95 @@ +package org.argeo.cms.text; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsNames; +import org.argeo.cms.CmsTypes; + +/** Based on HTML with a few Wiki-like shortcuts. */ +public class IdentityTextInterpreter implements TextInterpreter, CmsNames { + + @Override + public void write(Item item, String content) { + try { + if (item instanceof Node) { + Node node = (Node) item; + if (node.isNodeType(CmsTypes.CMS_STYLED)) { + String raw = convertToStorage(node, content); + validateBeforeStoring(raw); + node.setProperty(CMS_CONTENT, raw); + } else { + throw new CmsException("Don't know how to interpret " + + node); + } + } else {// property + Property property = (Property) item; + property.setValue(content); + } + // item.getSession().save(); + } catch (RepositoryException e) { + throw new CmsException("Cannot set content on " + item, e); + } + } + + @Override + public String read(Item item) { + try { + String raw = raw(item); + return convertFromStorage(item, raw); + } catch (RepositoryException e) { + throw new CmsException("Cannot get " + item + " for edit", e); + } + } + + @Override + public String raw(Item item) { + try { + item.getSession().refresh(true); + if (item instanceof Node) { + Node node = (Node) item; + if (node.isNodeType(CmsTypes.CMS_STYLED)) { + // WORKAROUND FOR BROKEN PARARAPHS + if (!node.hasProperty(CMS_CONTENT)) { + node.setProperty(CMS_CONTENT, ""); + node.getSession().save(); + } + + return node.getProperty(CMS_CONTENT).getString(); + } else { + throw new CmsException("Don't know how to interpret " + + node); + } + } else {// property + Property property = (Property) item; + return property.getString(); + } + } catch (RepositoryException e) { + throw new CmsException("Cannot get " + item + " content", e); + } + } + + // EXTENSIBILITY + /** + * To be overridden, in order to make sure that only valid strings are being + * stored. + */ + protected void validateBeforeStoring(String raw) { + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertToStorage(Item item, String content) + throws RepositoryException { + return content; + + } + + /** To be overridden, in order to support additional formatting. */ + protected String convertFromStorage(Item item, String content) + throws RepositoryException { + return content; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/Img.java b/org.argeo.cms.ui/src/org/argeo/cms/text/Img.java new file mode 100644 index 000000000..e0cf21624 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/Img.java @@ -0,0 +1,151 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.ui.internal.JcrFileUploadReceiver; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.NodePart; +import org.argeo.cms.viewers.Section; +import org.argeo.cms.viewers.SectionPart; +import org.argeo.cms.widgets.EditableImage; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.fileupload.FileUploadReceiver; +import org.eclipse.rap.rwt.service.ServerPushSession; +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** An image within the Argeo Text framework */ +public class Img extends EditableImage implements SectionPart, NodePart { + private static final long serialVersionUID = 6233572783968188476L; + + private final Section section; + + private final CmsImageManager imageManager; + private FileUploadHandler currentUploadHandler = null; + private FileUploadListener fileUploadListener; + + public Img(Composite parent, int swtStyle, Node imgNode, + Point preferredImageSize) throws RepositoryException { + this(Section.findSection(parent), parent, swtStyle, imgNode, + preferredImageSize); + setStyle(TextStyles.TEXT_IMAGE); + } + + public Img(Composite parent, int swtStyle, Node imgNode) + throws RepositoryException { + this(Section.findSection(parent), parent, swtStyle, imgNode, null); + setStyle(TextStyles.TEXT_IMAGE); + } + + Img(Section section, Composite parent, int swtStyle, Node imgNode, + Point preferredImageSize) throws RepositoryException { + super(parent, swtStyle, imgNode, false, preferredImageSize); + this.section = section; + imageManager = CmsUtils.getCmsView().getImageManager(); + CmsUtils.style(this, TextStyles.TEXT_IMG); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + try { + return createImageChooser(box, style); + } catch (RepositoryException e) { + throw new CmsException("Cannot create image chooser", e); + } + } else { + return createLabel(box, style); + } + } + + @Override + public synchronized void stopEditing() { + super.stopEditing(); + fileUploadListener = null; + } + + @Override + protected synchronized Boolean load(Control lbl) { + try { + Node imgNode = getNode(); + boolean loaded = imageManager.load(imgNode, lbl, + getPreferredImageSize()); + // getParent().layout(); + return loaded; + } catch (RepositoryException e) { + throw new CmsException("Cannot load " + getNodeId() + + " from image manager", e); + } + } + + protected Control createImageChooser(Composite box, String style) + throws RepositoryException { + // FileDialog fileDialog = new FileDialog(getShell()); + // fileDialog.open(); + // String fileName = fileDialog.getFileName(); + CmsImageManager imageManager = CmsUtils.getCmsView().getImageManager(); + Node node = getNode(); + JcrFileUploadReceiver receiver = new JcrFileUploadReceiver( + node.getParent(), node.getName() + '[' + node.getIndex() + ']', + imageManager); + if (currentUploadHandler != null) + currentUploadHandler.dispose(); + currentUploadHandler = prepareUpload(receiver); + final ServerPushSession pushSession = new ServerPushSession(); + final FileUpload fileUpload = new FileUpload(box, SWT.NONE); + CmsUtils.style(fileUpload, style); + fileUpload.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -9158471843941668562L; + + @Override + public void widgetSelected(SelectionEvent e) { + pushSession.start(); + fileUpload.submit(currentUploadHandler.getUploadUrl()); + } + }); + return fileUpload; + } + + protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { + final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); + if (fileUploadListener != null) + uploadHandler.addUploadListener(fileUploadListener); + return uploadHandler; + } + + @Override + public Section getSection() { + return section; + } + + public void setFileUploadListener(FileUploadListener fileUploadListener) { + this.fileUploadListener = fileUploadListener; + if (currentUploadHandler != null) + currentUploadHandler.addUploadListener(fileUploadListener); + } + + @Override + public Node getItem() throws RepositoryException { + return getNode(); + } + + @Override + public String getPartId() { + return getNodeId(); + } + + @Override + public String toString() { + return "Img #" + getPartId(); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/Paragraph.java b/org.argeo.cms.ui/src/org/argeo/cms/text/Paragraph.java new file mode 100644 index 000000000..a7a7964f4 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/Paragraph.java @@ -0,0 +1,41 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.Section; +import org.argeo.cms.viewers.SectionPart; +import org.argeo.cms.widgets.EditableText; + +public class Paragraph extends EditableText implements SectionPart { + private static final long serialVersionUID = 3746457776229542887L; + + private final TextSection section; + + public Paragraph(TextSection section, int style, Node node) + throws RepositoryException { + super(section, style, node); + this.section = section; + CmsUtils.style(this, TextStyles.TEXT_PARAGRAPH); + } + + public Section getSection() { + return section; + } + + @Override + public String getPartId() { + return getNodeId(); + } + + @Override + public Node getItem() throws RepositoryException { + return getNode(); + } + + @Override + public String toString() { + return "Paragraph #" + getPartId(); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/StandardTextEditor.java b/org.argeo.cms.ui/src/org/argeo/cms/text/StandardTextEditor.java new file mode 100644 index 000000000..3d1dc3f6f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/StandardTextEditor.java @@ -0,0 +1,48 @@ +package org.argeo.cms.text; + +import static javax.jcr.Property.JCR_TITLE; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsTypes; +import org.argeo.cms.ui.internal.text.AbstractTextViewer; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.Section; +import org.eclipse.swt.widgets.Composite; + +/** Text editor where sections and subsections can be managed by the user. */ +public class StandardTextEditor extends AbstractTextViewer { + private static final long serialVersionUID = 6049661610883342325L; + + public StandardTextEditor(Composite parent, int style, Node textNode, + CmsEditable cmsEditable) throws RepositoryException { + super(new TextSection(parent, style, textNode), style, cmsEditable); + refresh(); + getMainSection().setLayoutData(CmsUtils.fillWidth()); + } + + @Override + protected void initModel(Node textNode) throws RepositoryException { + if (isFlat()) + textNode.addNode(CMS_P).addMixin(CmsTypes.CMS_STYLED); + else + textNode.setProperty(JCR_TITLE, textNode.getName()); + } + + @Override + protected Boolean isModelInitialized(Node textNode) + throws RepositoryException { + return textNode.hasProperty(Property.JCR_TITLE) + || textNode.hasNode(CMS_P) + || (!isFlat() && textNode.hasNode(CMS_H)); + } + + @Override + public Section getMainSection() { + // TODO Auto-generated method stub + return super.getMainSection(); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/TextEditorHeader.java b/org.argeo.cms.ui/src/org/argeo/cms/text/TextEditorHeader.java new file mode 100644 index 000000000..e70a4d06e --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/TextEditorHeader.java @@ -0,0 +1,90 @@ +package org.argeo.cms.text; + +import java.util.Observable; +import java.util.Observer; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** Adds editing capabilities to a page editing text */ +public class TextEditorHeader implements SelectionListener, Observer { + private static final long serialVersionUID = 4186756396045701253L; + + private final CmsEditable cmsEditable; + private Button publish; + + private Composite parent; + private Composite display; + private Object layoutData; + + public TextEditorHeader(CmsEditable cmsEditable, Composite parent, int style) { + this.cmsEditable = cmsEditable; + this.parent = parent; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + refresh(); + } + + protected void refresh() { + if (display != null && !display.isDisposed()) + display.dispose(); + display = null; + publish = null; + if (cmsEditable.isEditing()) { + display = new Composite(parent, SWT.NONE); + // display.setBackgroundMode(SWT.INHERIT_NONE); + display.setLayoutData(layoutData); + display.setLayout(CmsUtils.noSpaceGridLayout()); + CmsUtils.style(display, TextStyles.TEXT_EDITOR_HEADER); + publish = new Button(display, SWT.FLAT | SWT.PUSH); + publish.setText(getPublishButtonLabel()); + CmsUtils.style(publish, TextStyles.TEXT_EDITOR_HEADER); + publish.addSelectionListener(this); + display.moveAbove(null); + } + parent.layout(); + } + + private String getPublishButtonLabel() { + if (cmsEditable.isEditing()) + return "Publish"; + else + return "Edit"; + } + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.getSource() == publish) { + if (cmsEditable.isEditing()) { + cmsEditable.stopEditing(); + } else { + cmsEditable.startEditing(); + } + // publish.setText(getPublishButtonLabel()); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) { + // publish.setText(getPublishButtonLabel()); + refresh(); + } + } + + public void setLayoutData(Object layoutData) { + this.layoutData = layoutData; + if (display != null && !display.isDisposed()) + display.setLayoutData(layoutData); + } + +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/TextInterpreter.java b/org.argeo.cms.ui/src/org/argeo/cms/text/TextInterpreter.java new file mode 100644 index 000000000..f39a2b329 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/TextInterpreter.java @@ -0,0 +1,12 @@ +package org.argeo.cms.text; + +import javax.jcr.Item; + +/** Convert from/to data layer to/from presentation layer. */ +public interface TextInterpreter { + public String raw(Item item); + + public String read(Item item); + + public void write(Item item, String content); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/TextSection.java b/org.argeo.cms.ui/src/org/argeo/cms/text/TextSection.java new file mode 100644 index 000000000..ac93e4b65 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/TextSection.java @@ -0,0 +1,52 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsNames; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.Section; +import org.eclipse.swt.widgets.Composite; + +public class TextSection extends Section implements CmsNames { + private static final long serialVersionUID = -8625209546243220689L; + private String defaultTextStyle = TextStyles.TEXT_DEFAULT; + private String titleStyle; + + public TextSection(Composite parent, int style, Node node) + throws RepositoryException { + this(parent, findSection(parent), style, node); + } + + public TextSection(TextSection section, int style, Node node) + throws RepositoryException { + this(section, section.getParentSection(), style, node); + } + + private TextSection(Composite parent, Section parentSection, int style, + Node node) throws RepositoryException { + super(parent, parentSection, style, node); + CmsUtils.style(this, TextStyles.TEXT_SECTION); + } + + public String getDefaultTextStyle() { + return defaultTextStyle; + } + + public String getTitleStyle() { + if (titleStyle != null) + return titleStyle; + // TODO make base H styles configurable + Integer relativeDepth = getRelativeDepth(); + return relativeDepth == 0 ? TextStyles.TEXT_TITLE : TextStyles.TEXT_H + + relativeDepth; + } + + public void setDefaultTextStyle(String defaultTextStyle) { + this.defaultTextStyle = defaultTextStyle; + } + + public void setTitleStyle(String titleStyle) { + this.titleStyle = titleStyle; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/TextStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/text/TextStyles.java new file mode 100644 index 000000000..44c3ad054 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/TextStyles.java @@ -0,0 +1,37 @@ +package org.argeo.cms.text; + +/** Styles references in the CSS. */ +public interface TextStyles { + /** The whole page area */ + public final static String TEXT_AREA = "text_area"; + /** Area providing controls for editing text */ + public final static String TEXT_EDITOR_HEADER = "text_editor_header"; + /** The styled composite for editing the text */ + public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite"; + /** A section */ + public final static String TEXT_SECTION = "text_section"; + /** A paragraph */ + public final static String TEXT_PARAGRAPH = "text_paragraph"; + /** An image */ + public final static String TEXT_IMG = "text_img"; + /** The dialog to edit styled paragraph */ + public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog"; + + /* + * DEFAULT TEXT STYLES + */ + /** Default style for text body */ + public final static String TEXT_DEFAULT = "text_default"; + /** Fixed-width, typically code */ + public final static String TEXT_PRE = "text_pre"; + /** Quote */ + public final static String TEXT_QUOTE = "text_quote"; + /** Title */ + public final static String TEXT_TITLE = "text_title"; + /** Header (to be dynamically completed with the depth, e.g. text_h1) */ + public final static String TEXT_H = "text_h"; + + /** Default style for images */ + public final static String TEXT_IMAGE = "text_image"; + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/text/WikiPage.java b/org.argeo.cms.ui/src/org/argeo/cms/text/WikiPage.java new file mode 100644 index 000000000..38af381a5 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/text/WikiPage.java @@ -0,0 +1,67 @@ +package org.argeo.cms.text; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsNames; +import org.argeo.cms.CmsTypes; +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.util.CmsLink; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.JcrVersionCmsEditable; +import org.argeo.cms.widgets.ScrolledPage; +import org.argeo.jcr.JcrUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Display the text of the context, and provide an editor if the user can edit. */ +public class WikiPage implements CmsUiProvider, CmsNames { + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + CmsEditable cmsEditable = new JcrVersionCmsEditable(context); + if (cmsEditable.canEdit()) + new TextEditorHeader(cmsEditable, parent, SWT.NONE) + .setLayoutData(CmsUtils.fillWidth()); + + ScrolledPage page = new ScrolledPage(parent, SWT.NONE); + page.setLayout(CmsUtils.noSpaceGridLayout()); + GridData textGd = CmsUtils.fillAll(); + page.setLayoutData(textGd); + + if (context.isNodeType(CmsTypes.CMS_TEXT)) { + new StandardTextEditor(page, SWT.NONE, context, cmsEditable); + } else if (context.isNodeType(NodeType.NT_FOLDER) + || context.getPath().equals("/")) { + parent.setBackgroundMode(SWT.INHERIT_NONE); + if (context.getSession().hasPermission(context.getPath(), + Session.ACTION_ADD_NODE)) { + Node indexNode = JcrUtils.getOrAdd(context, CMS_INDEX, + CmsTypes.CMS_TEXT); + new StandardTextEditor(page, SWT.NONE, indexNode, cmsEditable); + textGd.heightHint = 400; + + for (NodeIterator ni = context.getNodes(); ni.hasNext();) { + Node textNode = ni.nextNode(); + if (textNode.isNodeType(NodeType.NT_FOLDER)) + new CmsLink(textNode.getName() + "/", + textNode.getPath()).createUi(parent, textNode); + } + for (NodeIterator ni = context.getNodes(); ni.hasNext();) { + Node textNode = ni.nextNode(); + if (textNode.isNodeType(CmsTypes.CMS_TEXT) + && !textNode.getName().equals(CMS_INDEX)) + new CmsLink(textNode.getName(), textNode.getPath()) + .createUi(parent, textNode); + } + } + } + return page; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java new file mode 100644 index 000000000..ffc162b0f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java @@ -0,0 +1,42 @@ +package org.argeo.cms.ui.internal; + +import org.argeo.cms.CmsStyles; +import org.argeo.cms.maintenance.MaintenanceUi; +import org.argeo.cms.ui.internal.rwt.UserUi; +import org.argeo.node.NodeState; +import org.argeo.util.LangUtils; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +public class Activator implements BundleActivator { + + // avoid dependency to RWT OSGi + private final static String CONTEXT_NAME_PROP = "contextName"; + + private static ServiceTracker nodeState; + + // @Override + public void start(BundleContext bc) throws Exception { + // UI + bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), + LangUtils.init(CONTEXT_NAME_PROP, "system")); + bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.init(CONTEXT_NAME_PROP, "user")); + + nodeState = new ServiceTracker<>(bc, NodeState.class, null); + nodeState.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + if (nodeState != null) { + nodeState.close(); + nodeState = null; + } + } + + public static NodeState getNodeState() { + return nodeState.getService(); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/ImageManagerImpl.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/ImageManagerImpl.java new file mode 100644 index 000000000..92f237a4c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/ImageManagerImpl.java @@ -0,0 +1,263 @@ +package org.argeo.cms.ui.internal; + +import static javax.jcr.Node.JCR_CONTENT; +import static javax.jcr.Property.JCR_DATA; +import static javax.jcr.nodetype.NodeType.NT_FILE; +import static javax.jcr.nodetype.NodeType.NT_RESOURCE; +import static org.argeo.cms.CmsConstants.NO_IMAGE_SIZE; +import static org.argeo.cms.CmsTypes.CMS_STYLED; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.activation.MimetypesFileTypeMap; +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.CmsNames; +import org.argeo.cms.CmsTypes; +import org.argeo.cms.util.CmsUtils; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.service.ResourceManager; +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; + +/** Manages only public images so far. */ +public class ImageManagerImpl implements CmsImageManager, CmsNames { + private final static Log log = LogFactory.getLog(ImageManagerImpl.class); + private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + + public Boolean load(Node node, Control control, Point preferredSize) + throws RepositoryException { + Point imageSize = getImageSize(node); + Point size; + String imgTag = null; + if (preferredSize == null || imageSize.x == 0 || imageSize.y == 0 + || (preferredSize.x == 0 && preferredSize.y == 0)) { + if (imageSize.x != 0 && imageSize.y != 0) { + // actual image size if completely known + size = imageSize; + } else { + // no image if not completely known + size = resizeTo(NO_IMAGE_SIZE, + preferredSize != null ? preferredSize : imageSize); + imgTag = CmsUtils.noImg(size); + } + + } else if (preferredSize.x != 0 && preferredSize.y != 0) { + // given size if completely provided + size = preferredSize; + } else { + // at this stage : + // image is completely known + assert imageSize.x != 0 && imageSize.y != 0; + // one and only one of the dimension as been specified + assert preferredSize.x == 0 || preferredSize.y == 0; + size = resizeTo(imageSize, preferredSize); + } + + boolean loaded = false; + if (control == null) + return loaded; + + if (control instanceof Label) { + if (imgTag == null) { + // IMAGE RETRIEVED HERE + imgTag = getImageTag(node, size); + // + if (imgTag == null) + imgTag = CmsUtils.noImg(size); + else + loaded = true; + } + + Label lbl = (Label) control; + lbl.setText(imgTag); + // lbl.setSize(size); + } else if (control instanceof FileUpload) { + FileUpload lbl = (FileUpload) control; + lbl.setImage(CmsUtils.noImage(size)); + lbl.setSize(size); + return loaded; + } else + loaded = false; + + return loaded; + } + + private Point resizeTo(Point orig, Point constraints) { + if (constraints.x != 0 && constraints.y != 0) { + return constraints; + } else if (constraints.x == 0 && constraints.y == 0) { + return orig; + } else if (constraints.y == 0) {// force width + return new Point(constraints.x, + scale(orig.y, orig.x, constraints.x)); + } else if (constraints.x == 0) {// force height + return new Point(scale(orig.x, orig.y, constraints.y), + constraints.y); + } + throw new CmsException("Cannot resize " + orig + " to " + constraints); + } + + private int scale(int origDimension, int otherDimension, int otherConstraint) { + return Math.round(origDimension + * divide(otherConstraint, otherDimension)); + } + + private float divide(int a, int b) { + return ((float) a) / ((float) b); + } + + public Point getImageSize(Node node) throws RepositoryException { + return new Point(node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node + .getProperty(CMS_IMAGE_WIDTH).getLong() : 0, + node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node.getProperty( + CMS_IMAGE_HEIGHT).getLong() : 0); + } + + /** @return null if not available */ + @Override + public String getImageTag(Node node) throws RepositoryException { + return getImageTag(node, getImageSize(node)); + } + + private String getImageTag(Node node, Point size) + throws RepositoryException { + StringBuilder buf = getImageTagBuilder(node, size); + if (buf == null) + return null; + return buf.append("/>").toString(); + } + + /** @return null if not available */ + @Override + public StringBuilder getImageTagBuilder(Node node, Point size) + throws RepositoryException { + return getImageTagBuilder(node, Integer.toString(size.x), + Integer.toString(size.y)); + } + + /** @return null if not available */ + private StringBuilder getImageTagBuilder(Node node, String width, + String height) throws RepositoryException { + String url = getImageUrl(node); + if (url == null) + return null; + return CmsUtils.imgBuilder(url, width, height); + } + + /** @return null if not available */ + @Override + public String getImageUrl(Node node) throws RepositoryException { + return CmsUtils.getDataPath(node); + // String name = getResourceName(node); + // ResourceManager resourceManager = RWT.getResourceManager(); + // if (!resourceManager.isRegistered(name)) { + // InputStream inputStream = null; + // Binary binary = getImageBinary(node); + // if (binary == null) + // return null; + // try { + // inputStream = binary.getStream(); + // resourceManager.register(name, inputStream); + // } finally { + // IOUtils.closeQuietly(inputStream); + // JcrUtils.closeQuietly(binary); + // } + // if (log.isTraceEnabled()) + // log.trace("Registered image " + name); + // } + // return resourceManager.getLocation(name); + } + + protected String getResourceName(Node node) throws RepositoryException { + String workspace = node.getSession().getWorkspace().getName(); + if (node.hasNode(JCR_CONTENT)) + return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier(); + else + return workspace + '_' + node.getIdentifier(); + } + + public Binary getImageBinary(Node node) throws RepositoryException { + if (node.isNodeType(NT_FILE)) + return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary(); + else if (node.isNodeType(CMS_STYLED) && node.hasProperty(CMS_DATA)) { + return node.getProperty(CMS_DATA).getBinary(); + } else { + return null; + } + } + + public Image getSwtImage(Node node) throws RepositoryException { + InputStream inputStream = null; + Binary binary = getImageBinary(node); + if (binary == null) + return null; + try { + inputStream = binary.getStream(); + return new Image(Display.getCurrent(), inputStream); + } finally { + IOUtils.closeQuietly(inputStream); + JcrUtils.closeQuietly(binary); + } + } + + @Override + public String uploadImage(Node parentNode, String fileName, InputStream in) + throws RepositoryException { + InputStream inputStream = null; + try { + String previousResourceName = null; + if (parentNode.hasNode(fileName)) { + Node node = parentNode.getNode(fileName); + previousResourceName = getResourceName(node); + if (node.hasNode(JCR_CONTENT)) { + node.getNode(JCR_CONTENT).remove(); + node.addNode(JCR_CONTENT, NT_RESOURCE); + } + } + + byte[] arr = IOUtils.toByteArray(in); + Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr); + fileNode.addMixin(CmsTypes.CMS_IMAGE); + + inputStream = new ByteArrayInputStream(arr); + ImageData id = new ImageData(inputStream); + fileNode.setProperty(CMS_IMAGE_WIDTH, id.width); + fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height); + fileNode.setProperty(Property.JCR_MIMETYPE, + fileTypeMap.getContentType(fileName)); + fileNode.getSession().save(); + + // reset resource manager + ResourceManager resourceManager = RWT.getResourceManager(); + if (previousResourceName != null + && resourceManager.isRegistered(previousResourceName)) { + resourceManager.unregister(previousResourceName); + if (log.isDebugEnabled()) + log.debug("Unregistered image " + previousResourceName); + } + return getImageUrl(fileNode); + } catch (IOException e) { + throw new CmsException("Cannot upload image " + fileName + " in " + + parentNode, e); + } finally { + IOUtils.closeQuietly(inputStream); + } + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java new file mode 100644 index 000000000..3892f94df --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java @@ -0,0 +1,82 @@ +package org.argeo.cms.ui.internal; + +import java.util.ArrayList; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +@Deprecated +class JcrContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -1333678161322488674L; + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput == null) + return; + if (!(newInput instanceof Node)) + throw new CmsException("Input " + newInput + " must be a node"); + } + + @Override + public Object[] getElements(Object inputElement) { + try { + Node node = (Node) inputElement; + ArrayList arr = new ArrayList(); + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) { + arr.add(nit.nextNode()); + } + return arr.toArray(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public Object[] getChildren(Object parentElement) { + try { + Node node = (Node) parentElement; + ArrayList arr = new ArrayList(); + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) { + arr.add(nit.nextNode()); + } + return arr.toArray(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public Object getParent(Object element) { + try { + Node node = (Node) element; + if (node.getName().equals("")) + return null; + else + return node.getParent(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public boolean hasChildren(Object element) { + try { + Node node = (Node) element; + return node.hasNodes(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java new file mode 100644 index 000000000..2b157eb61 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java @@ -0,0 +1,82 @@ +package org.argeo.cms.ui.internal; + +import static javax.jcr.nodetype.NodeType.NT_FILE; + +import java.io.IOException; +import java.io.InputStream; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.io.FilenameUtils; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.CmsNames; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadReceiver; + +public class JcrFileUploadReceiver extends FileUploadReceiver implements + CmsNames { + private final Node parentNode; + private final String nodeName; + private final CmsImageManager imageManager; + + /** If nodeName is null, use the uploaded file name */ + public JcrFileUploadReceiver(Node parentNode, String nodeName, + CmsImageManager imageManager) { + super(); + this.parentNode = parentNode; + this.nodeName = nodeName; + this.imageManager = imageManager; + } + + @Override + public void receive(InputStream stream, FileDetails details) + throws IOException { + try { + String fileName = nodeName != null ? nodeName : details + .getFileName(); + String contentType = details.getContentType(); + if (isImage(details.getFileName(), contentType)) { + imageManager.uploadImage(parentNode, fileName, stream); + return; + // InputStream inputStream = new ByteArrayInputStream(arr); + // ImageData id = new ImageData(inputStream); + // fileNode.addMixin(CmsTypes.CMS_IMAGE); + // fileNode.setProperty(CMS_IMAGE_WIDTH, id.width); + // fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height); + } + + Node fileNode; + if (parentNode.hasNode(fileName)) { + fileNode = parentNode.getNode(fileName); + if (!fileNode.isNodeType(NT_FILE)) + fileNode.remove(); + } + fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream); + + if (contentType != null) { + fileNode.addMixin(NodeType.MIX_MIMETYPE); + fileNode.setProperty(Property.JCR_MIMETYPE, contentType); + } + processNewFile(fileNode); + fileNode.getSession().save(); + } catch (RepositoryException e) { + throw new CmsException("cannot receive " + details, e); + } + } + + protected Boolean isImage(String fileName, String contentType) { + String ext = FilenameUtils.getExtension(fileName); + return ext != null + && (ext.equals("png") || ext.equalsIgnoreCase("jpg")); + } + + protected void processNewFile(Node node) { + + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java new file mode 100644 index 000000000..3bb1fdf15 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java @@ -0,0 +1,73 @@ +package org.argeo.cms.ui.internal; + +import javax.jcr.RepositoryException; + +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.widgets.EditableImage; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** NOT working yet. */ +public class SimpleEditableImage extends EditableImage { + private static final long serialVersionUID = -5689145523114022890L; + + private String src; + private Point imageSize; + + public SimpleEditableImage(Composite parent, int swtStyle) { + super(parent, swtStyle); + // load(getControl()); + getParent().layout(); + } + + public SimpleEditableImage(Composite parent, int swtStyle, String src, + Point imageSize) { + super(parent, swtStyle); + this.src = src; + this.imageSize = imageSize; + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + return createText(box, style); + } else { + return createLabel(box, style); + } + } + + protected String createImgTag() throws RepositoryException { + String imgTag; + if (src != null) + imgTag = CmsUtils.img(src, imageSize); + else + imgTag = CmsUtils.noImg(imageSize != null ? imageSize + : NO_IMAGE_SIZE); + return imgTag; + } + + protected Text createText(Composite box, String style) { + Text text = new Text(box, getStyle()); + CmsUtils.style(text, style); + return text; + } + + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } + + public Point getImageSize() { + return imageSize; + } + + public void setImageSize(Point imageSize) { + this.imageSize = imageSize; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/rwt/UserUi.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/rwt/UserUi.java new file mode 100644 index 000000000..88cd17b81 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/rwt/UserUi.java @@ -0,0 +1,14 @@ +package org.argeo.cms.ui.internal.rwt; + +import org.argeo.cms.util.LoginEntryPoint; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.Application.OperationMode; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; + +public class UserUi implements ApplicationConfiguration { + @Override + public void configure(Application application) { + application.setOperationMode(OperationMode.SWT_COMPATIBILITY); + application.addEntryPoint("/login", LoginEntryPoint.class, null); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/AbstractTextViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/AbstractTextViewer.java new file mode 100644 index 000000000..c4b6060f9 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/AbstractTextViewer.java @@ -0,0 +1,892 @@ +package org.argeo.cms.ui.internal.text; + +import static javax.jcr.Property.JCR_TITLE; +import static org.argeo.cms.util.CmsUtils.fillWidth; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.Observer; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.CmsNames; +import org.argeo.cms.CmsTypes; +import org.argeo.cms.text.Img; +import org.argeo.cms.text.Paragraph; +import org.argeo.cms.text.TextInterpreter; +import org.argeo.cms.text.TextSection; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.viewers.AbstractPageViewer; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.NodePart; +import org.argeo.cms.viewers.PropertyPart; +import org.argeo.cms.viewers.Section; +import org.argeo.cms.viewers.SectionPart; +import org.argeo.cms.widgets.EditableImage; +import org.argeo.cms.widgets.EditableText; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadEvent; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** Base class for text viewers and editors. */ +public abstract class AbstractTextViewer extends AbstractPageViewer implements + CmsNames, KeyListener, Observer { + private static final long serialVersionUID = -2401274679492339668L; + private final static Log log = LogFactory.getLog(AbstractTextViewer.class); + + private final Section mainSection; + + private TextInterpreter textInterpreter = new TextInterpreterImpl(); + private CmsImageManager imageManager = CmsUtils.getCmsView() + .getImageManager(); + + private FileUploadListener fileUploadListener; + private TextContextMenu styledTools; + + private final boolean flat; + + protected AbstractTextViewer(Section parent, int style, + CmsEditable cmsEditable) { + super(parent, style, cmsEditable); + flat = SWT.FLAT == (style & SWT.FLAT); + + if (getCmsEditable().canEdit()) { + fileUploadListener = new FUL(); + styledTools = new TextContextMenu(this, parent.getDisplay()); + } + this.mainSection = parent; + initModelIfNeeded(mainSection.getNode()); + // layout(this.mainSection); + } + + @Override + public Control getControl() { + return mainSection; + } + + protected void refresh(Control control) throws RepositoryException { + if (!(control instanceof Section)) + return; + Section section = (Section) control; + if (section instanceof TextSection) { + CmsUtils.clear(section); + Node node = section.getNode(); + TextSection textSection = (TextSection) section; + if (node.hasProperty(Property.JCR_TITLE)) { + if (section.getHeader() == null) + section.createHeader(); + if (node.hasProperty(Property.JCR_TITLE)) { + SectionTitle title = newSectionTitle(textSection, node); + title.setLayoutData(CmsUtils.fillWidth()); + updateContent(title); + } + } + + for (NodeIterator ni = node.getNodes(CMS_P); ni.hasNext();) { + Node child = ni.nextNode(); + final SectionPart sectionPart; + if (child.isNodeType(CmsTypes.CMS_IMAGE) + || child.isNodeType(NodeType.NT_FILE)) { + sectionPart = newImg(textSection, child); + } else if (child.isNodeType(CmsTypes.CMS_STYLED)) { + sectionPart = newParagraph(textSection, child); + } else { + sectionPart = newSectionPart(textSection, child); + if (sectionPart == null) + throw new CmsException("Unsupported node " + child); + // TODO list node types in exception + } + if (sectionPart instanceof Control) + ((Control) sectionPart).setLayoutData(CmsUtils.fillWidth()); + } + + if (!flat) + for (NodeIterator ni = section.getNode().getNodes(CMS_H); ni + .hasNext();) { + Node child = ni.nextNode(); + if (child.isNodeType(CmsTypes.CMS_SECTION)) { + TextSection newSection = new TextSection(section, + SWT.NONE, child); + newSection.setLayoutData(CmsUtils.fillWidth()); + refresh(newSection); + } + } + } else { + for (Section s : section.getSubSections().values()) + refresh(s); + } + // section.layout(); + } + + /** To be overridden in order to provide additional SectionPart types */ + protected SectionPart newSectionPart(TextSection textSection, Node node) { + return null; + } + + // CRUD + protected Paragraph newParagraph(TextSection parent, Node node) + throws RepositoryException { + Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node); + updateContent(paragraph); + paragraph.setLayoutData(fillWidth()); + paragraph.setMouseListener(getMouseListener()); + return paragraph; + } + + protected Img newImg(TextSection parent, Node node) + throws RepositoryException { + Img img = new Img(parent, parent.getStyle(), node) { + private static final long serialVersionUID = 1297900641952417540L; + + @Override + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + + @Override + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, + SWT.DEFAULT)); + } + }; + img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + updateContent(img); + img.setMouseListener(getMouseListener()); + return img; + } + + protected SectionTitle newSectionTitle(TextSection parent, Node node) + throws RepositoryException { + SectionTitle title = new SectionTitle(parent.getHeader(), + parent.getStyle(), node.getProperty(JCR_TITLE)); + updateContent(title); + title.setMouseListener(getMouseListener()); + return title; + } + + protected SectionTitle prepareSectionTitle(Section newSection, + String titleText) throws RepositoryException { + Node sectionNode = newSection.getNode(); + if (!sectionNode.hasProperty(JCR_TITLE)) + sectionNode.setProperty(Property.JCR_TITLE, ""); + getTextInterpreter().write(sectionNode.getProperty(Property.JCR_TITLE), + titleText); + if (newSection.getHeader() == null) + newSection.createHeader(); + SectionTitle sectionTitle = newSectionTitle((TextSection) newSection, + sectionNode); + return sectionTitle; + } + + protected void updateContent(EditablePart part) throws RepositoryException { + if (part instanceof SectionPart) { + SectionPart sectionPart = (SectionPart) part; + Node partNode = sectionPart.getNode(); + + if (part instanceof StyledControl + && (sectionPart.getSection() instanceof TextSection)) { + TextSection section = (TextSection) sectionPart.getSection(); + StyledControl styledControl = (StyledControl) part; + if (partNode.isNodeType(CmsTypes.CMS_STYLED)) { + String style = partNode.hasProperty(CMS_STYLE) ? partNode + .getProperty(CMS_STYLE).getString() : section + .getDefaultTextStyle(); + styledControl.setStyle(style); + } + } + // use control AFTER setting style, since it may have been reset + + if (part instanceof EditableText) { + EditableText paragraph = (EditableText) part; + if (paragraph == getEdited()) + paragraph.setText(textInterpreter.read(partNode)); + else + paragraph.setText(textInterpreter.raw(partNode)); + } else if (part instanceof EditableImage) { + EditableImage editableImage = (EditableImage) part; + imageManager.load(partNode, part.getControl(), + editableImage.getPreferredImageSize()); + } + } else if (part instanceof SectionTitle) { + SectionTitle title = (SectionTitle) part; + title.setStyle(title.getSection().getTitleStyle()); + // use control AFTER setting style + if (title == getEdited()) + title.setText(textInterpreter.read(title.getProperty())); + else + title.setText(textInterpreter.raw(title.getProperty())); + } + } + + // OVERRIDDEN FROM PARENT VIEWER + @Override + protected void save(EditablePart part) throws RepositoryException { + if (part instanceof EditableText) { + EditableText et = (EditableText) part; + String text = ((Text) et.getControl()).getText(); + + String[] lines = text.split("[\r\n]+"); + assert lines.length != 0; + saveLine(part, lines[0]); + if (lines.length > 1) { + ArrayList toLayout = new ArrayList(); + if (part instanceof Paragraph) { + Paragraph currentParagraph = (Paragraph) et; + Section section = currentParagraph.getSection(); + Node sectionNode = section.getNode(); + Node currentParagraphN = currentParagraph.getNode(); + for (int i = 1; i < lines.length; i++) { + Node newNode = sectionNode.addNode(CMS_P); + newNode.addMixin(CmsTypes.CMS_STYLED); + saveLine(newNode, lines[i]); + // second node was create as last, if it is not the next + // one, it + // means there are some in between and we can take the + // one at + // index+1 for the re-order + if (newNode.getIndex() > currentParagraphN.getIndex() + 1) { + sectionNode.orderBefore(p(newNode.getIndex()), + p(currentParagraphN.getIndex() + 1)); + } + Paragraph newParagraph = newParagraph( + (TextSection) section, newNode); + newParagraph.moveBelow(currentParagraph); + toLayout.add(newParagraph); + + currentParagraph = newParagraph; + currentParagraphN = newNode; + } + persistChanges(sectionNode); + } + // TODO or rather return the created paragarphs? + layout(toLayout.toArray(new Control[toLayout.size()])); + } + } + } + + protected void saveLine(EditablePart part, String line) { + if (part instanceof NodePart) { + saveLine(((NodePart) part).getNode(), line); + } else if (part instanceof PropertyPart) { + saveLine(((PropertyPart) part).getProperty(), line); + } else { + throw new CmsException("Unsupported part " + part); + } + } + + protected void saveLine(Item item, String line) { + line = line.trim(); + textInterpreter.write(item, line); + } + + @Override + protected void prepare(EditablePart part, Object caretPosition) { + Control control = part.getControl(); + if (control instanceof Text) { + Text text = (Text) control; + if (caretPosition != null) + if (caretPosition instanceof Integer) + text.setSelection((Integer) caretPosition); + else if (caretPosition instanceof Point) { + // TODO find a way to position the caret at the right place + } + text.setData(RWT.ACTIVE_KEYS, new String[] { "BACKSPACE", "ESC", + "TAB", "SHIFT+TAB", "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT", + "ALT+ARROW_UP", "ALT+ARROW_DOWN", "RETURN", "CTRL+RETURN", + "ENTER", "DELETE" }); + text.setData(RWT.CANCEL_KEYS, new String[] { "RETURN", + "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT" }); + text.addKeyListener(this); + } else if (part instanceof Img) { + ((Img) part).setFileUploadListener(fileUploadListener); + } + } + + // REQUIRED BY CONTEXT MENU + void setParagraphStyle(Paragraph paragraph, String style) { + try { + Node paragraphNode = paragraph.getNode(); + paragraphNode.setProperty(CMS_STYLE, style); + persistChanges(paragraphNode); + updateContent(paragraph); + layout(paragraph); + } catch (RepositoryException e1) { + throw new CmsException("Cannot set style " + style + " on " + + paragraph, e1); + } + } + + void deletePart(SectionPart paragraph) { + try { + Node paragraphNode = paragraph.getNode(); + Section section = paragraph.getSection(); + Session session = paragraphNode.getSession(); + paragraphNode.remove(); + session.save(); + if (paragraph instanceof Control) + ((Control) paragraph).dispose(); + layout(section); + } catch (RepositoryException e1) { + throw new CmsException("Cannot delete " + paragraph, e1); + } + } + + String getRawParagraphText(Paragraph paragraph) { + return textInterpreter.raw(paragraph.getNode()); + } + + // COMMANDS + protected void splitEdit() { + checkEdited(); + try { + if (getEdited() instanceof Paragraph) { + Paragraph paragraph = (Paragraph) getEdited(); + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + String txt = text.getText(); + String first = txt.substring(0, caretPosition); + String second = txt.substring(caretPosition); + Node firstNode = paragraph.getNode(); + Node sectionNode = firstNode.getParent(); + firstNode.setProperty(CMS_CONTENT, first); + Node secondNode = sectionNode.addNode(CMS_P); + secondNode.addMixin(CmsTypes.CMS_STYLED); + // second node was create as last, if it is not the next one, it + // means there are some in between and we can take the one at + // index+1 for the re-order + if (secondNode.getIndex() > firstNode.getIndex() + 1) { + sectionNode.orderBefore(p(secondNode.getIndex()), + p(firstNode.getIndex() + 1)); + } + + // if we die in between, at least we still have the whole text + // in the first node + try { + textInterpreter.write(secondNode, second); + textInterpreter.write(firstNode, first); + } catch (Exception e) { + // so that no additional nodes are created: + JcrUtils.discardUnderlyingSessionQuietly(firstNode); + throw e; + } + + persistChanges(firstNode); + + Paragraph secondParagraph = paragraphSplitted(paragraph, + secondNode); + edit(secondParagraph, 0); + } else if (getEdited() instanceof SectionTitle) { + SectionTitle sectionTitle = (SectionTitle) getEdited(); + Text text = (Text) sectionTitle.getControl(); + String txt = text.getText(); + int caretPosition = text.getCaretPosition(); + Section section = sectionTitle.getSection(); + Node sectionNode = section.getNode(); + Node paragraphNode = sectionNode.addNode(CMS_P); + paragraphNode.addMixin(CmsTypes.CMS_STYLED); + textInterpreter.write(paragraphNode, + txt.substring(caretPosition)); + textInterpreter.write( + sectionNode.getProperty(Property.JCR_TITLE), + txt.substring(0, caretPosition)); + sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1)); + persistChanges(sectionNode); + + Paragraph paragraph = sectionTitleSplitted(sectionTitle, + paragraphNode); + // section.layout(); + edit(paragraph, 0); + } + } catch (RepositoryException e) { + throw new CmsException("Cannot split " + getEdited(), e); + } + } + + protected void mergeWithPrevious() { + checkEdited(); + try { + Paragraph paragraph = (Paragraph) getEdited(); + Text text = (Text) paragraph.getControl(); + String txt = text.getText(); + Node paragraphNode = paragraph.getNode(); + if (paragraphNode.getIndex() == 1) + return;// do nothing + Node sectionNode = paragraphNode.getParent(); + Node previousNode = sectionNode + .getNode(p(paragraphNode.getIndex() - 1)); + String previousTxt = textInterpreter.read(previousNode); + textInterpreter.write(previousNode, previousTxt + txt); + paragraphNode.remove(); + persistChanges(sectionNode); + + Paragraph previousParagraph = paragraphMergedWithPrevious( + paragraph, previousNode); + edit(previousParagraph, previousTxt.length()); + } catch (RepositoryException e) { + throw new CmsException("Cannot stop editing", e); + } + } + + protected void mergeWithNext() { + checkEdited(); + try { + Paragraph paragraph = (Paragraph) getEdited(); + Text text = (Text) paragraph.getControl(); + String txt = text.getText(); + Node paragraphNode = paragraph.getNode(); + Node sectionNode = paragraphNode.getParent(); + NodeIterator paragraphNodes = sectionNode.getNodes(CMS_P); + long size = paragraphNodes.getSize(); + if (paragraphNode.getIndex() == size) + return;// do nothing + Node nextNode = sectionNode + .getNode(p(paragraphNode.getIndex() + 1)); + String nextTxt = textInterpreter.read(nextNode); + textInterpreter.write(paragraphNode, txt + nextTxt); + + Section section = paragraph.getSection(); + Paragraph removed = (Paragraph) section.getSectionPart(nextNode + .getIdentifier()); + + nextNode.remove(); + persistChanges(sectionNode); + + paragraphMergedWithNext(paragraph, removed); + edit(paragraph, txt.length()); + } catch (RepositoryException e) { + throw new CmsException("Cannot stop editing", e); + } + } + + protected synchronized void upload(EditablePart part) { + try { + if (part instanceof SectionPart) { + SectionPart sectionPart = (SectionPart) part; + Node partNode = sectionPart.getNode(); + int partIndex = partNode.getIndex(); + Section section = sectionPart.getSection(); + Node sectionNode = section.getNode(); + + if (part instanceof Paragraph) { + Node newNode = sectionNode.addNode(CMS_P, NodeType.NT_FILE); + newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); + JcrUtils.copyBytesAsFile(sectionNode, + p(newNode.getIndex()), new byte[0]); + if (partIndex < newNode.getIndex() - 1) { + // was not last + sectionNode.orderBefore(p(newNode.getIndex()), + p(partIndex - 1)); + } + // sectionNode.orderBefore(p(partNode.getIndex()), + // p(newNode.getIndex())); + persistChanges(sectionNode); + Img img = newImg((TextSection) section, newNode); + edit(img, null); + layout(img.getControl()); + } else if (part instanceof Img) { + if (getEdited() == part) + return; + edit(part, null); + layout(part.getControl()); + } + } + } catch (RepositoryException e) { + throw new CmsException("Cannot upload", e); + } + } + + protected void deepen() { + if (flat) + return; + checkEdited(); + try { + if (getEdited() instanceof Paragraph) { + Paragraph paragraph = (Paragraph) getEdited(); + Text text = (Text) paragraph.getControl(); + String txt = text.getText(); + Node paragraphNode = paragraph.getNode(); + Section section = paragraph.getSection(); + Node sectionNode = section.getNode(); + // main title + if (section == mainSection && section instanceof TextSection + && paragraphNode.getIndex() == 1 + && !sectionNode.hasProperty(JCR_TITLE)) { + SectionTitle sectionTitle = prepareSectionTitle(section, + txt); + edit(sectionTitle, 0); + return; + } + Node newSectionNode = sectionNode.addNode(CMS_H, + CmsTypes.CMS_SECTION); + sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1)); + + int paragraphIndex = paragraphNode.getIndex(); + String sectionPath = sectionNode.getPath(); + String newSectionPath = newSectionNode.getPath(); + while (sectionNode.hasNode(p(paragraphIndex + 1))) { + Node parag = sectionNode.getNode(p(paragraphIndex + 1)); + sectionNode.getSession().move( + sectionPath + '/' + p(paragraphIndex + 1), + newSectionPath + '/' + CMS_P); + SectionPart sp = section.getSectionPart(parag + .getIdentifier()); + if (sp instanceof Control) + ((Control) sp).dispose(); + } + // create property + newSectionNode.setProperty(Property.JCR_TITLE, ""); + getTextInterpreter().write( + newSectionNode.getProperty(Property.JCR_TITLE), txt); + + TextSection newSection = new TextSection(section, + section.getStyle(), newSectionNode); + newSection.setLayoutData(CmsUtils.fillWidth()); + newSection.moveBelow(paragraph); + + // dispose + paragraphNode.remove(); + paragraph.dispose(); + + refresh(newSection); + newSection.getParent().layout(); + layout(newSection); + persistChanges(sectionNode); + } else if (getEdited() instanceof SectionTitle) { + SectionTitle sectionTitle = (SectionTitle) getEdited(); + Section section = sectionTitle.getSection(); + Section parentSection = section.getParentSection(); + if (parentSection == null) + return;// cannot deepen main section + Node sectionN = section.getNode(); + Node parentSectionN = parentSection.getNode(); + if (sectionN.getIndex() == 1) + return;// cannot deepen first section + Node previousSectionN = parentSectionN.getNode(h(sectionN + .getIndex() - 1)); + NodeIterator subSections = previousSectionN.getNodes(CMS_H); + int subsectionsCount = (int) subSections.getSize(); + previousSectionN.getSession().move( + sectionN.getPath(), + previousSectionN.getPath() + "/" + + h(subsectionsCount + 1)); + section.dispose(); + TextSection newSection = new TextSection(section, + section.getStyle(), sectionN); + refresh(newSection); + persistChanges(previousSectionN); + } + } catch (RepositoryException e) { + throw new CmsException("Cannot deepen " + getEdited(), e); + } + } + + protected void undeepen() { + if (flat) + return; + checkEdited(); + try { + if (getEdited() instanceof Paragraph) { + upload(getEdited()); + } else if (getEdited() instanceof SectionTitle) { + SectionTitle sectionTitle = (SectionTitle) getEdited(); + Section section = sectionTitle.getSection(); + Node sectionNode = section.getNode(); + Section parentSection = section.getParentSection(); + if (parentSection == null) + return;// cannot undeepen main section + + // choose in which section to merge + Section mergedSection; + if (sectionNode.getIndex() == 1) + mergedSection = section.getParentSection(); + else { + Map parentSubsections = parentSection + .getSubSections(); + ArrayList
lst = new ArrayList
( + parentSubsections.values()); + mergedSection = lst.get(sectionNode.getIndex() - 1); + } + Node mergedNode = mergedSection.getNode(); + boolean mergedHasSubSections = mergedNode.hasNode(CMS_H); + + // title as paragraph + Node newParagrapheNode = mergedNode.addNode(CMS_P); + newParagrapheNode.addMixin(CmsTypes.CMS_STYLED); + if (mergedHasSubSections) + mergedNode.orderBefore(p(newParagrapheNode.getIndex()), + h(1)); + String txt = getTextInterpreter().read( + sectionNode.getProperty(Property.JCR_TITLE)); + getTextInterpreter().write(newParagrapheNode, txt); + // move + NodeIterator paragraphs = sectionNode.getNodes(CMS_P); + while (paragraphs.hasNext()) { + Node p = paragraphs.nextNode(); + SectionPart sp = section.getSectionPart(p.getIdentifier()); + if (sp instanceof Control) + ((Control) sp).dispose(); + mergedNode.getSession().move(p.getPath(), + mergedNode.getPath() + '/' + CMS_P); + if (mergedHasSubSections) + mergedNode.orderBefore(p(p.getIndex()), h(1)); + } + + Iterator
subsections = section.getSubSections() + .values().iterator(); + // NodeIterator sections = sectionNode.getNodes(CMS_H); + while (subsections.hasNext()) { + Section subsection = subsections.next(); + Node s = subsection.getNode(); + mergedNode.getSession().move(s.getPath(), + mergedNode.getPath() + '/' + CMS_H); + subsection.dispose(); + } + + // remove section + section.getNode().remove(); + section.dispose(); + + refresh(mergedSection); + mergedSection.getParent().layout(); + layout(mergedSection); + persistChanges(mergedNode); + } + } catch (RepositoryException e) { + throw new CmsException("Cannot undeepen " + getEdited(), e); + } + } + + // UI CHANGES + protected Paragraph paragraphSplitted(Paragraph paragraph, Node newNode) + throws RepositoryException { + Section section = paragraph.getSection(); + updateContent(paragraph); + Paragraph newParagraph = newParagraph((TextSection) section, newNode); + newParagraph.setLayoutData(CmsUtils.fillWidth()); + newParagraph.moveBelow(paragraph); + layout(paragraph.getControl(), newParagraph.getControl()); + return newParagraph; + } + + protected Paragraph sectionTitleSplitted(SectionTitle sectionTitle, + Node newNode) throws RepositoryException { + updateContent(sectionTitle); + Paragraph newParagraph = newParagraph(sectionTitle.getSection(), + newNode); + // we assume beforeFirst is not null since there was a sectionTitle + newParagraph.moveBelow(sectionTitle.getSection().getHeader()); + layout(sectionTitle.getControl(), newParagraph.getControl()); + return newParagraph; + } + + protected Paragraph paragraphMergedWithPrevious(Paragraph removed, + Node remaining) throws RepositoryException { + Section section = removed.getSection(); + removed.dispose(); + + Paragraph paragraph = (Paragraph) section.getSectionPart(remaining + .getIdentifier()); + updateContent(paragraph); + layout(paragraph.getControl()); + return paragraph; + } + + protected void paragraphMergedWithNext(Paragraph remaining, + Paragraph removed) throws RepositoryException { + removed.dispose(); + updateContent(remaining); + layout(remaining.getControl()); + } + + // UTILITIES + protected String p(Integer index) { + StringBuilder sb = new StringBuilder(6); + sb.append(CMS_P).append('[').append(index).append(']'); + return sb.toString(); + } + + protected String h(Integer index) { + StringBuilder sb = new StringBuilder(5); + sb.append(CMS_H).append('[').append(index).append(']'); + return sb.toString(); + } + + // GETTERS / SETTERS + public Section getMainSection() { + return mainSection; + } + + public boolean isFlat() { + return flat; + } + + public TextInterpreter getTextInterpreter() { + return textInterpreter; + } + + // KEY LISTENER + @Override + public void keyPressed(KeyEvent ke) { + if (log.isTraceEnabled()) + log.trace(ke); + + if (getEdited() == null) + return; + boolean altPressed = (ke.stateMask & SWT.ALT) != 0; + boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0; + boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0; + + try { + // Common + if (ke.keyCode == SWT.ESC) { + cancelEdit(); + } else if (ke.character == '\r') { + splitEdit(); + } else if (ke.character == 'S') { + if (ctrlPressed) + saveEdit(); + } else if (ke.character == '\t') { + if (!shiftPressed) { + deepen(); + } else if (shiftPressed) { + undeepen(); + } + } else { + if (getEdited() instanceof Paragraph) { + Paragraph paragraph = (Paragraph) getEdited(); + Section section = paragraph.getSection(); + if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) { + edit(section.nextSectionPart(paragraph), 0); + } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) { + edit(section.previousSectionPart(paragraph), 0); + } else if (ke.character == SWT.BS) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + if (caretPosition == 0) { + mergeWithPrevious(); + } + } else if (ke.character == SWT.DEL) { + Text text = (Text) paragraph.getControl(); + int caretPosition = text.getCaretPosition(); + int charcount = text.getCharCount(); + if (caretPosition == charcount) { + mergeWithNext(); + } + } + } + } + } catch (Exception e) { + ke.doit = false; + notifyEditionException(e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (getCmsEditable().canEdit()) { + if (getCmsEditable().isEditing() + && !(getEdited() instanceof Img)) { + if (source == mainSection) + return; + EditablePart part = findDataParent(source); + upload(part); + } else { + getCmsEditable().startEditing(); + } + } + } + } + + @Override + public void mouseDown(MouseEvent e) { + if (getCmsEditable().isEditing()) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + EditablePart composite = findDataParent(source); + Point point = new Point(e.x, e.y); + if (!(composite instanceof Img)) + edit(composite, source.toDisplay(point)); + } else if (e.button == 3) { + EditablePart composite = findDataParent((Control) e + .getSource()); + if (styledTools != null) + styledTools.show(composite, new Point(e.x, e.y)); + } + } + } + + @Override + public void mouseUp(MouseEvent e) { + } + } + + // FILE UPLOAD LISTENER + private class FUL implements FileUploadListener { + public void uploadProgress(FileUploadEvent event) { + // TODO Monitor upload progress + } + + public void uploadFailed(FileUploadEvent event) { + throw new CmsException("Upload failed " + event, + event.getException()); + } + + public void uploadFinished(FileUploadEvent event) { + for (FileDetails file : event.getFileDetails()) { + if (log.isDebugEnabled()) + log.debug("Received: " + file.getFileName()); + } + mainSection.getDisplay().syncExec(new Runnable() { + @Override + public void run() { + saveEdit(); + } + }); + FileUploadHandler uploadHandler = (FileUploadHandler) event + .getSource(); + uploadHandler.dispose(); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/MarkupValidatorCopy.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/MarkupValidatorCopy.java new file mode 100644 index 000000000..9bced0d5e --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/MarkupValidatorCopy.java @@ -0,0 +1,184 @@ +package org.argeo.cms.ui.internal.text; + +import java.io.StringReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.argeo.cms.forms.FormPageViewer; +import org.eclipse.rap.rwt.SingletonUtil; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Copy of RAP v2.3 since it is in an internal package. + * + * FIXME made public to enable validation from the {@link FormPageViewer} + */ +public class MarkupValidatorCopy { + + // Used by Eclipse Scout project + public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled"; + + private static final String DTD = createDTD(); + private static final Map SUPPORTED_ELEMENTS = createSupportedElementsMap(); + private final SAXParser saxParser; + + public static MarkupValidatorCopy getInstance() { + return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class); + } + + public MarkupValidatorCopy() { + saxParser = createSAXParser(); + } + + public void validate(String text) { + StringBuilder markup = new StringBuilder(); + markup.append(DTD); + markup.append(""); + markup.append(text); + markup.append(""); + InputSource inputSource = new InputSource(new StringReader( + markup.toString())); + try { + saxParser.parse(inputSource, new MarkupHandler()); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalArgumentException("Failed to parse markup text", + exception); + } + } + + public static boolean isValidationDisabledFor(Widget widget) { + return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED)); + } + + private static SAXParser createSAXParser() { + SAXParser result = null; + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + try { + result = parserFactory.newSAXParser(); + } catch (Exception exception) { + throw new RuntimeException("Failed to create SAX parser", exception); + } + return result; + } + + private static String createDTD() { + StringBuilder result = new StringBuilder(); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append("]>"); + return result.toString(); + } + + private static Map createSupportedElementsMap() { + Map result = new HashMap(); + result.put("html", new String[0]); + result.put("br", new String[0]); + result.put("b", new String[] { "style" }); + result.put("strong", new String[] { "style" }); + result.put("i", new String[] { "style" }); + result.put("em", new String[] { "style" }); + result.put("sub", new String[] { "style" }); + result.put("sup", new String[] { "style" }); + result.put("big", new String[] { "style" }); + result.put("small", new String[] { "style" }); + result.put("del", new String[] { "style" }); + result.put("ins", new String[] { "style" }); + result.put("code", new String[] { "style" }); + result.put("samp", new String[] { "style" }); + result.put("kbd", new String[] { "style" }); + result.put("var", new String[] { "style" }); + result.put("cite", new String[] { "style" }); + result.put("dfn", new String[] { "style" }); + result.put("q", new String[] { "style" }); + result.put("abbr", new String[] { "style", "title" }); + result.put("span", new String[] { "style" }); + result.put("img", new String[] { "style", "src", "width", "height", + "title", "alt" }); + result.put("a", new String[] { "style", "href", "target", "title" }); + return result; + } + + private static class MarkupHandler extends DefaultHandler { + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) { + checkSupportedElements(name, attributes); + checkSupportedAttributes(name, attributes); + checkMandatoryAttributes(name, attributes); + } + + private static void checkSupportedElements(String elementName, + Attributes attributes) { + if (!SUPPORTED_ELEMENTS.containsKey(elementName)) { + throw new IllegalArgumentException( + "Unsupported element in markup text: " + elementName); + } + } + + private static void checkSupportedAttributes(String elementName, + Attributes attributes) { + if (attributes.getLength() > 0) { + List supportedAttributes = Arrays + .asList(SUPPORTED_ELEMENTS.get(elementName)); + int index = 0; + String attributeName = attributes.getQName(index); + while (attributeName != null) { + if (!supportedAttributes.contains(attributeName)) { + String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; + message = MessageFormat.format(message, new Object[] { + attributeName, elementName }); + throw new IllegalArgumentException(message); + } + index++; + attributeName = attributes.getQName(index); + } + } + } + + private static void checkMandatoryAttributes(String elementName, + Attributes attributes) { + checkIntAttribute(elementName, attributes, "img", "width"); + checkIntAttribute(elementName, attributes, "img", "height"); + } + + private static void checkIntAttribute(String elementName, + Attributes attributes, String checkedElementName, + String checkedAttributeName) { + if (checkedElementName.equals(elementName)) { + String attribute = attributes.getValue(checkedAttributeName); + try { + Integer.parseInt(attribute); + } catch (NumberFormatException exception) { + String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; + Object[] arguments = new Object[] { checkedAttributeName, + checkedElementName }; + message = MessageFormat.format(message, arguments); + throw new IllegalArgumentException(message); + } + } + } + + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/SectionTitle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/SectionTitle.java new file mode 100644 index 000000000..24861ee06 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/SectionTitle.java @@ -0,0 +1,39 @@ +package org.argeo.cms.ui.internal.text; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.cms.text.TextSection; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.PropertyPart; +import org.argeo.cms.widgets.EditableText; +import org.eclipse.swt.widgets.Composite; + +/** The title of a section. */ +public class SectionTitle extends EditableText implements EditablePart, + PropertyPart { + private static final long serialVersionUID = -1787983154946583171L; + + private final TextSection section; + + public SectionTitle(Composite parent, int swtStyle, Property title) + throws RepositoryException { + super(parent, swtStyle, title); + section = (TextSection) TextSection.findSection(this); + } + + public TextSection getSection() { + return section; + } + + // @Override + // public Property getProperty() throws RepositoryException { + // return getSection().getNode().getProperty(Property.JCR_TITLE); + // } + + @Override + public Property getItem() throws RepositoryException { + return getProperty(); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextContextMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextContextMenu.java new file mode 100644 index 000000000..4868b765a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextContextMenu.java @@ -0,0 +1,135 @@ +package org.argeo.cms.ui.internal.text; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.cms.CmsNames; +import org.argeo.cms.text.Paragraph; +import org.argeo.cms.text.TextStyles; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.SectionPart; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** Dialog to edit a text part. */ +class TextContextMenu extends Shell implements CmsNames, TextStyles { + private final static String[] DEFAULT_TEXT_STYLES = { + TextStyles.TEXT_DEFAULT, TextStyles.TEXT_PRE, TextStyles.TEXT_QUOTE }; + + private final AbstractTextViewer textViewer; + + private static final long serialVersionUID = -3826246895162050331L; + private List styleButtons = new ArrayList(); + + private Label deleteButton, publishButton, editButton; + + private EditablePart currentTextPart; + + public TextContextMenu(AbstractTextViewer textViewer, Display display) { + super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + this.textViewer = textViewer; + setLayout(new GridLayout()); + setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG); + + StyledToolMouseListener stml = new StyledToolMouseListener(); + if (textViewer.getCmsEditable().isEditing()) { + for (String style : DEFAULT_TEXT_STYLES) { + StyleButton styleButton = new StyleButton(this, SWT.WRAP); + styleButton.setData(RWT.CUSTOM_VARIANT, style); + styleButton.setData(RWT.MARKUP_ENABLED, true); + styleButton.addMouseListener(stml); + styleButtons.add(styleButton); + } + + // Delete + deleteButton = new Label(this, SWT.NONE); + deleteButton.setText("Delete"); + deleteButton.addMouseListener(stml); + + // Publish + publishButton = new Label(this, SWT.NONE); + publishButton.setText("Publish"); + publishButton.addMouseListener(stml); + } else if (textViewer.getCmsEditable().canEdit()) { + // Edit + editButton = new Label(this, SWT.NONE); + editButton.setText("Edit"); + editButton.addMouseListener(stml); + } + addShellListener(new ToolsShellListener()); + } + + public void show(EditablePart source, Point location) { + if (isVisible()) + setVisible(false); + + this.currentTextPart = source; + + if (currentTextPart instanceof Paragraph) { + final int size = 32; + String text = textViewer + .getRawParagraphText((Paragraph) currentTextPart); + String textToShow = text.length() > size ? text.substring(0, + size - 3) + "..." : text; + for (StyleButton styleButton : styleButtons) { + styleButton.setText(textToShow); + } + } + pack(); + layout(); + if (source instanceof Control) + setLocation(((Control) source).toDisplay(location.x, location.y)); + open(); + } + + class StyleButton extends Label { + private static final long serialVersionUID = 7731102609123946115L; + + public StyleButton(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + } + + class StyledToolMouseListener extends MouseAdapter { + private static final long serialVersionUID = 8516297091549329043L; + + @Override + public void mouseDown(MouseEvent e) { + Object eventSource = e.getSource(); + if (eventSource instanceof StyleButton) { + StyleButton sb = (StyleButton) e.getSource(); + String style = sb.getData(RWT.CUSTOM_VARIANT).toString(); + textViewer + .setParagraphStyle((Paragraph) currentTextPart, style); + } else if (eventSource == deleteButton) { + textViewer.deletePart((SectionPart) currentTextPart); + } else if (eventSource == editButton) { + textViewer.getCmsEditable().startEditing(); + } else if (eventSource == publishButton) { + textViewer.getCmsEditable().stopEditing(); + } + setVisible(false); + } + } + + class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter { + private static final long serialVersionUID = 8432350564023247241L; + + @Override + public void shellDeactivated(ShellEvent e) { + setVisible(false); + } + + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextInterpreterImpl.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextInterpreterImpl.java new file mode 100644 index 000000000..4a646c3fc --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/text/TextInterpreterImpl.java @@ -0,0 +1,33 @@ +package org.argeo.cms.ui.internal.text; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.cms.text.IdentityTextInterpreter; + +/** + * Text interpreter that sanitise and validates before saving, and support CMS + * specific formatting and integration. + */ +class TextInterpreterImpl extends IdentityTextInterpreter { + private MarkupValidatorCopy markupValidator = MarkupValidatorCopy + .getInstance(); + + @Override + protected void validateBeforeStoring(String raw) { + markupValidator.validate(raw); + } + + @Override + protected String convertToStorage(Item item, String content) + throws RepositoryException { + return super.convertToStorage(item, content); + } + + @Override + protected String convertFromStorage(Item item, String content) + throws RepositoryException { + return super.convertFromStorage(item, content); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java new file mode 100644 index 000000000..cda00efcd --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java @@ -0,0 +1,47 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.argeo.cms.CmsException; +import org.eclipse.rap.rwt.service.ResourceLoader; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */ +public class BundleResourceLoader implements ResourceLoader { + private final BundleContext bundleContext; + + public BundleResourceLoader(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public InputStream getResourceAsStream(String resourceName) + throws IOException { + // TODO deal with other bundles + Bundle bundle = bundleContext.getBundle(); + // String location = + // bundle.getLocation().substring("initial@reference:".length()); + // if (location.startsWith("file:")) { + // Path path = null; + // try { + // path = Paths.get(new URI(location)); + // } catch (URISyntaxException e) { + // e.printStackTrace(); + // } + // if (path != null) { + // Path resourcePath = path.resolve(resourceName); + // if (Files.exists(resourcePath)) + // return Files.newInputStream(resourcePath); + // } + // } + URL res = bundle.getResource(resourceName); + if (res == null) + throw new CmsException("Resource " + resourceName + + " not found in bundle " + bundle.getSymbolicName()); + return res.openStream(); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/CmsLink.java b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsLink.java new file mode 100644 index 000000000..14f3755ff --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsLink.java @@ -0,0 +1,274 @@ +package org.argeo.cms.util; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.jcr.Node; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsStyles; +import org.argeo.cms.CmsUiProvider; +import org.eclipse.gemini.blueprint.context.BundleContextAware; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.service.ResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.BundleContext; +import org.springframework.beans.factory.InitializingBean; + +/** A link to an internal or external location. */ +public class CmsLink implements CmsUiProvider, InitializingBean, + BundleContextAware { + private final static Log log = LogFactory.getLog(CmsLink.class); + + private String label; + private String custom; + private String target; + private String image; + private MouseListener mouseListener; + + private int verticalAlignment = SWT.CENTER; + + // internal + // private Boolean isUrl = false; + private Integer imageWidth, imageHeight; + + private BundleContext bundleContext; + + public CmsLink() { + super(); + } + + public CmsLink(String label, String target) { + this(label, target, null); + } + + public CmsLink(String label, String target, String custom) { + super(); + this.label = label; + this.target = target; + this.custom = custom; + afterPropertiesSet(); + } + + @Override + public void afterPropertiesSet() { + // if (target != null) { + // if (target.startsWith("/")) { + // isUrl = true; + // } else { + // try { + // new URL(target); + // isUrl = true; + // } catch (MalformedURLException e1) { + // isUrl = false; + // } + // } + // } + + if (image != null) { + ImageData image = loadImage(); + imageWidth = image.width; + imageHeight = image.height; + } + } + + /** @return {@link Composite} with a single {@link Label} child. */ + @Override + public Control createUi(final Composite parent, Node context) { + Composite comp = new Composite(parent, SWT.BOTTOM); + comp.setLayout(CmsUtils.noSpaceGridLayout()); + + Label link = new Label(comp, SWT.NONE); + link.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + GridData layoutData = new GridData(SWT.CENTER, verticalAlignment, true, + true); + if (image != null) { + layoutData.heightHint = imageHeight; + if (label == null) + layoutData.widthHint = imageWidth; + } + + link.setLayoutData(layoutData); + if (custom != null) { + comp.setData(RWT.CUSTOM_VARIANT, custom); + link.setData(RWT.CUSTOM_VARIANT, custom); + } else { + comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); + link.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); + } + + // label + StringBuilder labelText = new StringBuilder(); + if (target != null) { + labelText + .append(""); + } + if (image != null) { + registerImageIfNeeded(); + String imageLocation = RWT.getResourceManager().getLocation(image); + labelText.append(""); + + // final Image img = loadImage(parent.getDisplay()); + // link.setImage(img); + // link.addDisposeListener(new DListener(img)); + } + + if (label != null) { + // link.setText(label); + labelText.append(' ').append(label); + } + + if (target != null) + labelText.append(""); + + link.setText(labelText.toString()); + + // link.setCursor(link.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); + // CmsSession cmsSession = (CmsSession) parent.getDisplay().getData( + // CmsSession.KEY); + if (mouseListener != null) + link.addMouseListener(mouseListener); + + return comp; + } + + private void registerImageIfNeeded() { + ResourceManager resourceManager = RWT.getResourceManager(); + if (!resourceManager.isRegistered(image)) { + URL res = getImageUrl(); + InputStream inputStream = null; + try { + IOUtils.closeQuietly(inputStream); + inputStream = res.openStream(); + resourceManager.register(image, inputStream); + if (log.isTraceEnabled()) + log.trace("Registered image " + image); + } catch (Exception e) { + throw new CmsException("Cannot load image " + image, e); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } + + private ImageData loadImage() { + URL url = getImageUrl(); + ImageData result = null; + InputStream inputStream = null; + try { + inputStream = url.openStream(); + result = new ImageData(inputStream); + if (log.isTraceEnabled()) + log.trace("Loaded image " + image); + } catch (Exception e) { + throw new CmsException("Cannot load image " + image, e); + } finally { + IOUtils.closeQuietly(inputStream); + } + return result; + } + + private URL getImageUrl() { + URL url; + try { + // pure URL + url = new URL(image); + } catch (MalformedURLException e1) { + // in OSGi bundle + if (bundleContext == null) + throw new CmsException("No bundle context available"); + url = bundleContext.getBundle().getResource(image); + } + + if (url == null) + throw new CmsException("No image " + image + " available."); + + return url; + } + + public void setLabel(String label) { + this.label = label; + } + + public void setCustom(String custom) { + this.custom = custom; + } + + public void setTarget(String target) { + this.target = target; + // try { + // new URL(target); + // isUrl = true; + // } catch (MalformedURLException e1) { + // isUrl = false; + // } + } + + public void setImage(String image) { + this.image = image; + } + + @Override + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public void setMouseListener(MouseListener mouseListener) { + this.mouseListener = mouseListener; + } + + public void setvAlign(String vAlign) { + if ("bottom".equals(vAlign)) { + verticalAlignment = SWT.BOTTOM; + } else if ("top".equals(vAlign)) { + verticalAlignment = SWT.TOP; + } else if ("center".equals(vAlign)) { + verticalAlignment = SWT.CENTER; + } else { + throw new CmsException("Unsupported vertical allignment " + vAlign + + " (must be: top, bottom or center)"); + } + } + + // private class MListener extends MouseAdapter { + // private static final long serialVersionUID = 3634864186295639792L; + // + // @Override + // public void mouseDown(MouseEvent e) { + // if (e.button == 1) { + // } + // } + // } + // + // private class DListener implements DisposeListener { + // private static final long serialVersionUID = -3808587499269394812L; + // private final Image img; + // + // public DListener(Image img) { + // super(); + // this.img = img; + // } + // + // @Override + // public void widgetDisposed(DisposeEvent event) { + // img.dispose(); + // } + // + // } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/CmsUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsUtils.java new file mode 100644 index 000000000..78279cd4b --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsUtils.java @@ -0,0 +1,248 @@ +package org.argeo.cms.util; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsView; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.eclipse.ui.specific.UiContext; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.service.ResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Widget; + +/** Static utilities for the CMS framework. */ +public class CmsUtils implements CmsConstants { + private final static Log log = LogFactory.getLog(CmsUtils.class); + + /** + * The CMS view related to this display, or null if none is available from + * this call. + */ + public static CmsView getCmsView() { + return UiContext.getData(CmsView.KEY); + } + + public static StringBuilder getServerBaseUrl(HttpServletRequest request) { + try { + URL url = new URL(request.getRequestURL().toString()); + StringBuilder buf = new StringBuilder(); + buf.append(url.getProtocol()).append("://").append(url.getHost()); + if (url.getPort() != -1) + buf.append(':').append(url.getPort()); + return buf; + } catch (MalformedURLException e) { + throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); + } + } +// + public static String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append(getDataPath(node)); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + } +// FIXME + private final static String PATH_DATA = "/data"; + private final static String WEBDAV_PUBLIC = PATH_DATA + "/public"; + private final static String WEBDAV_PRIVATE = PATH_DATA + "/files"; + public static String getDataPath(Node node) throws RepositoryException { + assert node != null; + String userId = node.getSession().getUserID(); + if (log.isTraceEnabled()) + log.trace(userId + " : " + node.getPath()); + StringBuilder buf = new StringBuilder(); + boolean isAnonymous = userId.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS); + if (isAnonymous) + buf.append(WEBDAV_PUBLIC); + else + buf.append(WEBDAV_PRIVATE); + // TODO convey repo alias vie repository properties + return buf.append('/').append(ArgeoJcrConstants.ALIAS_NODE).append('/').append(node.getSession().getWorkspace().getName()) + .append(node.getPath()).toString(); + } +// +// public static String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { +// try { +// StringBuilder buf = getServerBaseUrl(request); +// buf.append('/').append('!').append(node.getPath()); +// return new URL(buf.toString()).toString(); +// } catch (MalformedURLException e) { +// throw new CmsException("Cannot build data URL for " + node, e); +// } +// // return request.getRequestURL().append('!').append(node.getPath()) +// // .toString(); +// } + + /** @deprecated Use rowData16px() instead. GridData should not be reused. */ + @Deprecated + public static RowData ROW_DATA_16px = new RowData(16, 16); + + public static GridLayout noSpaceGridLayout() { + return noSpaceGridLayout(new GridLayout()); + } + + public static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + // + // GRID DATA + // + public static GridData fillWidth() { + return grabWidth(SWT.FILL, SWT.FILL); + } + + public static GridData fillAll() { + return new GridData(SWT.FILL, SWT.FILL, true, true); + } + + public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, true, false); + } + + public static RowData rowData16px() { + return new RowData(16, 16); + } + + /** Style widget */ + public static void style(Widget widget, String style) { + widget.setData(CmsConstants.STYLE, style); + } + + /** Enable markups on widget */ + public static void markup(Widget widget) { + widget.setData(CmsConstants.MARKUP, true); + } + + public static void setItemHeight(Table table, int height) { + table.setData(CmsConstants.ITEM_HEIGHT, height); + } + + /** @return the path or null if not instrumented */ + public static String getDataPath(Widget widget) { + // JCR item + Object data = widget.getData(); + if (data != null && data instanceof Item) { + try { + return ((Item) data).getPath(); + } catch (RepositoryException e) { + throw new CmsException("Cannot find data path of " + data + " for " + widget); + } + } + + // JCR path + data = widget.getData(Property.JCR_PATH); + if (data != null) + return data.toString(); + + return null; + } + + /** Dispose all children of a Composite */ + public static void clear(Composite composite) { + for (Control child : composite.getChildren()) + child.dispose(); + } + + // + // JCR + // + public static Node getOrAddEmptyFile(Node parent, Enum child) throws RepositoryException { + if (has(parent, child)) + return child(parent, child); + return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]); + } + + public static Node child(Node parent, Enum en) throws RepositoryException { + return parent.getNode(en.name()); + } + + public static Boolean has(Node parent, Enum en) throws RepositoryException { + return parent.hasNode(en.name()); + } + + public static Node getOrAdd(Node parent, Enum en) throws RepositoryException { + return getOrAdd(parent, en, null); + } + + public static Node getOrAdd(Node parent, Enum en, String primaryType) throws RepositoryException { + if (has(parent, en)) + return child(parent, en); + else if (primaryType == null) + return parent.addNode(en.name()); + else + return parent.addNode(en.name(), primaryType); + } + + // IMAGES + public static String img(String src, String width, String height) { + return imgBuilder(src, width, height).append("/>").toString(); + } + + public static String img(String src, Point size) { + return img(src, Integer.toString(size.x), Integer.toString(size.y)); + } + + public static StringBuilder imgBuilder(String src, String width, String height) { + return new StringBuilder(64).append("not + * CmsLogin#createContent(), since it would lead to a stack overflow. + */ + protected void createLoginPage(Composite parent, CmsLogin login) { + login.defaultCreateContents(parent); + } + + protected void extendsCredentialsBlock(Composite credentialsBlock, + Locale selectedLocale, SelectionListener loginSelectionListener) { + + } + + @Override + public void navigateTo(String state) { + // TODO Auto-generated method stub + + } + + @Override + public void authChange(LoginContext loginContext) { + this.loginContext = loginContext; + } + + @Override + public void logout() { + if (loginContext == null) + throw new CmsException("Login context should not bet null"); + try { + loginContext.logout(); + } catch (LoginException e) { + throw new CmsException("Cannot log out", e); + } + } + + @Override + public final Subject getSubject() { + return subject; + } + + @Override + public void exception(Throwable e) { + // TODO Auto-generated method stub + + } + + @Override + public CmsImageManager getImageManager() { + // TODO Auto-generated method stub + return null; + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/MenuLink.java b/org.argeo.cms.ui/src/org/argeo/cms/util/MenuLink.java new file mode 100644 index 000000000..d491d3ca2 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/MenuLink.java @@ -0,0 +1,13 @@ +package org.argeo.cms.util; + +import org.argeo.cms.CmsStyles; + +/** + * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on + * a {@link CmsLink} when simple menus are used. + */ +public class MenuLink extends CmsLink { + public MenuLink() { + setCustom(CmsStyles.CMS_MENU_LINK); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/OpenUserMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/util/OpenUserMenu.java new file mode 100644 index 000000000..bb2bb3990 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/OpenUserMenu.java @@ -0,0 +1,17 @@ +package org.argeo.cms.util; + +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.widgets.Control; + +/** Open the user menu when clicked */ +public class OpenUserMenu extends MouseAdapter { + private static final long serialVersionUID = 3634864186295639792L; + + @Override + public void mouseDown(MouseEvent e) { + if (e.button == 1) { + new UserMenu((Control) e.getSource()); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java new file mode 100644 index 000000000..39e75070b --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java @@ -0,0 +1,376 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.Privilege; +import javax.jcr.version.VersionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.LifeCycleUiProvider; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.Application.OperationMode; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.eclipse.rap.rwt.application.EntryPoint; +import org.eclipse.rap.rwt.application.EntryPointFactory; +import org.eclipse.rap.rwt.application.ExceptionHandler; +import org.eclipse.rap.rwt.client.WebClient; +import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; +import org.eclipse.rap.rwt.service.ResourceLoader; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** A basic generic app based on {@link SimpleErgonomics}. */ +public class SimpleApp implements CmsConstants, ApplicationConfiguration { + private final static Log log = LogFactory.getLog(SimpleApp.class); + + private String contextName = null; + + private Map> branding = new HashMap>(); + private Map> styleSheets = new HashMap>(); + + private List resources = new ArrayList(); + + private BundleContext bundleContext; + + private Repository repository; + private String workspace = null; + private String jcrBasePath = "/"; + private List roPrincipals = Arrays.asList("anonymous", "everyone"); + private List rwPrincipals = Arrays.asList("everyone"); + + private CmsUiProvider header; + private Map pages = new LinkedHashMap(); + + private Integer headerHeight = 40; + + private ServiceRegistration appReg; + + public void configure(Application application) { + try { + StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader( + bundleContext); + BundleResourceLoader bundleRL = new BundleResourceLoader( + bundleContext); + + application.setOperationMode(OperationMode.SWT_COMPATIBILITY); + // application.setOperationMode(OperationMode.JEE_COMPATIBILITY); + + application.setExceptionHandler(new CmsExceptionHandler()); + + // loading animated gif + application.addResource(LOADING_IMAGE, + createResourceLoader(LOADING_IMAGE)); + // empty image + application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE)); + + for (String resource : resources) { + application.addResource(resource, bundleRL); + if (log.isTraceEnabled()) + log.trace("Resource " + resource); + } + + Map defaultBranding = null; + if (branding.containsKey("*")) + defaultBranding = branding.get("*"); + + // entry points + for (String page : pages.keySet()) { + Map properties = defaultBranding != null ? new HashMap( + defaultBranding) : new HashMap(); + if (branding.containsKey(page)) { + properties.putAll(branding.get(page)); + } + // favicon + if (properties.containsKey(WebClient.FAVICON)) { + String faviconRelPath = properties.get(WebClient.FAVICON); + application.addResource(faviconRelPath, + new BundleResourceLoader(bundleContext)); + if (log.isTraceEnabled()) + log.trace("Favicon " + faviconRelPath); + + } + + // page title + if (!properties.containsKey(WebClient.PAGE_TITLE)) { + if (page.length() > 0) + properties.put( + WebClient.PAGE_TITLE, + Character.toUpperCase(page.charAt(0)) + + page.substring(1)); + } + + // default body HTML + if (!properties.containsKey(WebClient.BODY_HTML)) + properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY); + + // + // ADD ENTRY POINT + // + application.addEntryPoint("/" + page, new CmsEntryPointFactory( + pages.get(page), repository, workspace, properties), + properties); + log.info("Page /" + page); + } + + // stylesheets + for (String themeId : styleSheets.keySet()) { + List cssLst = styleSheets.get(themeId); + if (log.isDebugEnabled()) + log.debug("Theme " + themeId); + for (String css : cssLst) { + application.addStyleSheet(themeId, css, styleSheetRL); + if (log.isTraceEnabled()) + log.trace(" CSS " + css); + } + + } + } catch (RuntimeException e) { + // Easier access to initialisation errors + log.error("Unexpected exception when configuring RWT application.", + e); + throw e; + } + } + + public void init() throws RepositoryException { + Session session = null; + try { + session = JcrUtils.loginOrCreateWorkspace(repository, workspace); + VersionManager vm = session.getWorkspace().getVersionManager(); + if (!vm.isCheckedOut("/")) + vm.checkout("/"); + JcrUtils.mkdirs(session, jcrBasePath); + for (String principal : rwPrincipals) + JcrUtils.addPrivilege(session, jcrBasePath, principal, + Privilege.JCR_WRITE); + for (String principal : roPrincipals) + JcrUtils.addPrivilege(session, jcrBasePath, principal, + Privilege.JCR_READ); + + for (String pageName : pages.keySet()) { + try { + initPage(session, pages.get(pageName)); + session.save(); + } catch (Exception e) { + throw new CmsException( + "Cannot initialize page " + pageName, e); + } + } + + } finally { + JcrUtils.logoutQuietly(session); + } + + // publish to OSGi + register(); + } + + protected void initPage(Session adminSession, CmsUiProvider page) + throws RepositoryException { + if (page instanceof LifeCycleUiProvider) + ((LifeCycleUiProvider) page).init(adminSession); + } + + public void destroy() { + for (String pageName : pages.keySet()) { + try { + CmsUiProvider page = pages.get(pageName); + if (page instanceof LifeCycleUiProvider) + ((LifeCycleUiProvider) page).destroy(); + } catch (Exception e) { + log.error("Cannot destroy page " + pageName, e); + } + } + } + + protected void register() { + Hashtable props = new Hashtable(); + if (contextName != null) + props.put("contextName", contextName); + appReg = bundleContext.registerService(ApplicationConfiguration.class, + this, props); + if (log.isDebugEnabled()) + log.debug("Registered " + (contextName == null ? "/" : contextName)); + } + + protected void unregister() { + appReg.unregister(); + if (log.isDebugEnabled()) + log.debug("Unregistered " + + (contextName == null ? "/" : contextName)); + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + public void setHeader(CmsUiProvider header) { + this.header = header; + } + + public void setPages(Map pages) { + this.pages = pages; + } + + public void setJcrBasePath(String basePath) { + this.jcrBasePath = basePath; + } + + public void setRoPrincipals(List roPrincipals) { + this.roPrincipals = roPrincipals; + } + + public void setRwPrincipals(List rwPrincipals) { + this.rwPrincipals = rwPrincipals; + } + + public void setHeaderHeight(Integer headerHeight) { + this.headerHeight = headerHeight; + } + + public void setBranding(Map> branding) { + this.branding = branding; + } + + public void setStyleSheets(Map> styleSheets) { + this.styleSheets = styleSheets; + } + + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public void setResources(List resources) { + this.resources = resources; + } + + public void setContextName(String contextName) { + this.contextName = contextName; + } + + class CmsExceptionHandler implements ExceptionHandler { + + @Override + public void handleException(Throwable throwable) { + // TODO be smarter + CmsUtils.getCmsView().exception(throwable); + } + + } + + private class CmsEntryPointFactory implements EntryPointFactory { + private final CmsUiProvider page; + private final Repository repository; + private final String workspace; + private final Map properties; + + public CmsEntryPointFactory(CmsUiProvider page, Repository repository, + String workspace, Map properties) { + this.page = page; + this.repository = repository; + this.workspace = workspace; + this.properties = properties; + } + + @Override + public EntryPoint create() { + SimpleErgonomics entryPoint = new SimpleErgonomics(repository, + workspace, jcrBasePath, page, properties) { + + @Override + protected void createAdminArea(Composite parent) { + Composite adminArea = new Composite(parent, SWT.NONE); + adminArea.setLayout(new FillLayout()); + Button refresh = new Button(adminArea, SWT.PUSH); + refresh.setText("Reload App"); + refresh.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -7671999525536351366L; + + @Override + public void widgetSelected(SelectionEvent e) { + long timeBeforeReload = 1000; + RWT.getClient() + .getService(JavaScriptExecutor.class) + .execute( + "setTimeout(function() { " + + "location.reload();" + + "}," + timeBeforeReload + + ");"); + reloadApp(); + } + }); + } + }; + // entryPoint.setState(""); + entryPoint.setHeader(header); + entryPoint.setHeaderHeight(headerHeight); + // CmsSession.current.set(entryPoint); + return entryPoint; + } + + private void reloadApp() { + new Thread("Refresh app") { + @Override + public void run() { + unregister(); + register(); + } + }.start(); + } + } + + private static ResourceLoader createResourceLoader(final String resourceName) { + return new ResourceLoader() { + public InputStream getResourceAsStream(String resourceName) + throws IOException { + return getClass().getClassLoader().getResourceAsStream( + resourceName); + } + }; + } + + // private static ResourceLoader createUrlResourceLoader(final URL url) { + // return new ResourceLoader() { + // public InputStream getResourceAsStream(String resourceName) + // throws IOException { + // return url.openStream(); + // } + // }; + // } + + /* + * TEXTS + */ + private static String DEFAULT_LOADING_BODY = "" + + "" + + ""; +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleCmsHeader.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleCmsHeader.java new file mode 100644 index 000000000..aa1bb7340 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleCmsHeader.java @@ -0,0 +1,88 @@ +package org.argeo.cms.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsStyles; +import org.argeo.cms.CmsUiProvider; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** A header in three parts */ +public class SimpleCmsHeader implements CmsUiProvider { + private List lead = new ArrayList(); + private List center = new ArrayList(); + private List end = new ArrayList(); + + private Boolean subPartsSameWidth = false; + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Composite header = new Composite(parent, SWT.NONE); + header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER); + header.setBackgroundMode(SWT.INHERIT_DEFAULT); + header.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(3, false))); + + configurePart(context, header, lead); + configurePart(context, header, center); + configurePart(context, header, end); + return header; + } + + protected void configurePart(Node context, Composite parent, + List partProviders) throws RepositoryException { + final int style; + final String custom; + if (lead == partProviders) { + style = SWT.LEAD; + custom = CmsStyles.CMS_HEADER_LEAD; + } else if (center == partProviders) { + style = SWT.CENTER; + custom = CmsStyles.CMS_HEADER_CENTER; + } else if (end == partProviders) { + style = SWT.END; + custom = CmsStyles.CMS_HEADER_END; + } else { + throw new CmsException("Unsupported part providers " + + partProviders); + } + + Composite part = new Composite(parent, SWT.NONE); + part.setData(RWT.CUSTOM_VARIANT, custom); + GridData gridData = new GridData(style, SWT.FILL, true, true); + part.setLayoutData(gridData); + part.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(partProviders + .size(), subPartsSameWidth))); + for (CmsUiProvider uiProvider : partProviders) { + Control subPart = uiProvider.createUi(part, context); + subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, + true)); + } + } + + public void setLead(List lead) { + this.lead = lead; + } + + public void setCenter(List center) { + this.center = center; + } + + public void setEnd(List end) { + this.end = end; + } + + public void setSubPartsSameWidth(Boolean subPartsSameWidth) { + this.subPartsSameWidth = subPartsSameWidth; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleDynamicPages.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleDynamicPages.java new file mode 100644 index 000000000..b6155cf31 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleDynamicPages.java @@ -0,0 +1,118 @@ +package org.argeo.cms.util; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsUiProvider; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class SimpleDynamicPages implements CmsUiProvider { + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + if (context == null) + throw new CmsException("Context cannot be null"); + parent.setLayout(new GridLayout(2, false)); + + // parent + if (!context.getPath().equals("/")) { + new CmsLink("..", context.getParent().getPath()).createUi(parent, + context); + new Label(parent, SWT.NONE).setText(context.getParent() + .getPrimaryNodeType().getName()); + } + + // context + Label contextL = new Label(parent, SWT.NONE); + contextL.setData(RWT.MARKUP_ENABLED, true); + contextL.setText("" + context.getName() + ""); + new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() + .getName()); + + // children + // Label childrenL = new Label(parent, SWT.NONE); + // childrenL.setData(RWT.MARKUP_ENABLED, true); + // childrenL.setText("Children:"); + // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, + // false, 2, 1)); + + for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { + Node child = nIt.nextNode(); + new CmsLink(child.getName(), child.getPath()).createUi(parent, + context); + + new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() + .getName()); + } + + // properties + // Label propsL = new Label(parent, SWT.NONE); + // propsL.setData(RWT.MARKUP_ENABLED, true); + // propsL.setText("Properties:"); + // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, + // 2, 1)); + for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { + Property property = pIt.nextProperty(); + + Label label = new Label(parent, SWT.NONE); + label.setText(property.getName()); + label.setToolTipText(JcrUtils + .getPropertyDefinitionAsString(property)); + + new Label(parent, SWT.NONE).setText(getPropAsString(property)); + } + + return null; + } + + private String getPropAsString(Property property) + throws RepositoryException { + String result = ""; + DateFormat timeFormatter = new SimpleDateFormat(""); + if (property.isMultiple()) { + result = getMultiAsString(property, ", "); + } else { + Value value = property.getValue(); + if (value.getType() == PropertyType.BINARY) + result = ""; + else if (value.getType() == PropertyType.DATE) + result = timeFormatter.format(value.getDate().getTime()); + else + result = value.getString(); + } + return result; + } + + private String getMultiAsString(Property property, String separator) + throws RepositoryException { + if (separator == null) + separator = "; "; + Value[] values = property.getValues(); + StringBuilder builder = new StringBuilder(); + for (Value val : values) { + String currStr = val.getString(); + if (!"".equals(currStr.trim())) + builder.append(currStr).append(separator); + } + if (builder.lastIndexOf(separator) >= 0) + return builder.substring(0, builder.length() - separator.length()); + else + return builder.toString(); + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java new file mode 100644 index 000000000..8ada61fdf --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java @@ -0,0 +1,140 @@ +package org.argeo.cms.util; + +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.AbstractCmsEntryPoint; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsImageManager; +import org.argeo.cms.CmsStyles; +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.UxContext; +import org.argeo.cms.ui.internal.ImageManagerImpl; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Simple header/body ergonomics. */ +public class SimpleErgonomics extends AbstractCmsEntryPoint { + private final static Log log = LogFactory.getLog(SimpleErgonomics.class); + + private boolean uiInitialized = false; + private Composite headerArea; + private Composite bodyArea; + private final CmsUiProvider uiProvider; + + private CmsUiProvider header; + private Integer headerHeight = 40; + + private CmsImageManager imageManager = new ImageManagerImpl(); + private UxContext uxContext = null; + + public SimpleErgonomics(Repository repository, String workspace, + String defaultPath, CmsUiProvider uiProvider, + Map factoryProperties) { + super(repository, workspace, defaultPath, factoryProperties); + this.uiProvider = uiProvider; + } + + @Override + protected void initUi(Composite parent) { + parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + parent.setLayout(CmsUtils.noSpaceGridLayout()); + + // createAdminArea(parent); + headerArea = new Composite(parent, SWT.NONE); + headerArea.setLayout(new FillLayout()); + GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false); + headerData.heightHint = headerHeight; + headerArea.setLayoutData(headerData); + + bodyArea = new Composite(parent, SWT.NONE); + bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY); + bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + bodyArea.setLayout(CmsUtils.noSpaceGridLayout()); + uxContext = new SimpleUxContext(); + uiInitialized = true; + refresh(); + } + + @Override + protected void refresh() { + if (!uiInitialized) + return; + if (getState() == null) + setState(""); + refreshHeader(); + refreshBody(); + if (log.isTraceEnabled()) + log.trace("UI refreshed " + getNode()); + } + + protected void createAdminArea(Composite parent) { + } + + protected void refreshHeader() { + for (Control child : headerArea.getChildren()) + child.dispose(); + try { + header.createUi(headerArea, getNode()); + } catch (RepositoryException e) { + throw new CmsException("Cannot refresh header", e); + } + headerArea.layout(true, true); + } + + protected void refreshBody() { + // Exception + Throwable exception = getException(); + if (exception != null) { + SystemNotifications systemNotifications = new SystemNotifications( + bodyArea); + systemNotifications.notifyException(exception); + resetException(); + return; + // TODO report + } + + // clear + for (Control child : bodyArea.getChildren()) + child.dispose(); + bodyArea.setLayout(CmsUtils.noSpaceGridLayout()); + + try { + uiProvider.createUi(bodyArea, getNode()); + } catch (RepositoryException e) { + throw new CmsException("Cannot refresh body", e); + } + + bodyArea.layout(true, true); + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public CmsImageManager getImageManager() { + return imageManager; + } + + public void setHeader(CmsUiProvider header) { + this.header = header; + } + + public void setHeaderHeight(Integer headerHeight) { + this.headerHeight = headerHeight; + } + + public void setImageManager(CmsImageManager imageManager) { + this.imageManager = imageManager; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleStaticPage.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleStaticPage.java new file mode 100644 index 000000000..6e09000e9 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleStaticPage.java @@ -0,0 +1,32 @@ +package org.argeo.cms.util; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsStyles; +import org.argeo.cms.CmsUiProvider; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class SimpleStaticPage implements CmsUiProvider { + private String text; + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Label textC = new Label(parent, SWT.WRAP); + textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT); + textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + textC.setText(text); + + return textC; + } + + public void setText(String text) { + this.text = text; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleUxContext.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleUxContext.java new file mode 100644 index 000000000..c5a6e6f4c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleUxContext.java @@ -0,0 +1,44 @@ +package org.argeo.cms.util; + +import org.argeo.cms.UxContext; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + +public class SimpleUxContext implements UxContext { + private Point size; + private Point small = new Point(400, 400); + + public SimpleUxContext() { + this(Display.getCurrent().getBounds()); + } + + public SimpleUxContext(Rectangle rect) { + this.size = new Point(rect.width, rect.height); + } + + public SimpleUxContext(Point size) { + this.size = size; + } + + @Override + public boolean isPortrait() { + return size.x >= size.y; + } + + @Override + public boolean isLandscape() { + return size.x < size.y; + } + + @Override + public boolean isSquare() { + return size.x == size.y; + } + + @Override + public boolean isSmall() { + return size.x <= small.x || size.y <= small.y; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java new file mode 100644 index 000000000..a7e3b6e84 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java @@ -0,0 +1,73 @@ +package org.argeo.cms.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.CmsException; +import org.eclipse.rap.rwt.service.ResourceLoader; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** {@link ResourceLoader} caching stylesheets. */ +public class StyleSheetResourceLoader implements ResourceLoader { + private final BundleContext bundleContext; + + private Map stylesheets = new LinkedHashMap(); + + public StyleSheetResourceLoader(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public InputStream getResourceAsStream(String resourceName) + throws IOException { + if (!stylesheets.containsKey(resourceName)) { + // TODO deal with other bundles + Bundle bundle = bundleContext.getBundle(); + // String location = + // bundle.getLocation().substring("initial@reference:".length()); + // if (location.startsWith("file:")) { + // Path path = null; + // try { + // path = Paths.get(new URI(location)); + // } catch (URISyntaxException e) { + // e.printStackTrace(); + // } + // if (path != null) { + // Path resourcePath = path.resolve(resourceName); + // if (Files.exists(resourcePath)) + // return Files.newInputStream(resourcePath); + // } + // } + URL res = bundle.getResource(resourceName); + if (res == null) + throw new CmsException("Resource " + resourceName + + " not found in bundle " + bundle.getSymbolicName()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(res.openStream(), out); + stylesheets.put(resourceName, new StyleSheet(out.toByteArray())); + } + return new ByteArrayInputStream(stylesheets.get(resourceName).getData()); + // return res.openStream(); + } + + private class StyleSheet { + private byte[] data; + + public StyleSheet(byte[] data) { + super(); + this.data = data; + } + + public byte[] getData() { + return data; + } + + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SystemNotifications.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SystemNotifications.java new file mode 100644 index 000000000..54e8eb544 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SystemNotifications.java @@ -0,0 +1,128 @@ +package org.argeo.cms.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsStyles; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** Shell displaying system notifications such as exceptions */ +public class SystemNotifications extends Shell implements CmsStyles, + MouseListener { + private static final long serialVersionUID = -8129377525216022683L; + + private Control source; + + public SystemNotifications(Control source) { + super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); + + this.source = source; + + // TODO UI + // setLocation(source.toDisplay(source.getSize().x - getSize().x, + // source.getSize().y)); + setLayout(new GridLayout()); + addMouseListener(this); + + addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + close(); + dispose(); + } + }); + + } + + public void notifyException(Throwable exception) { + Composite pane = this; + + Label lbl = new Label(pane, SWT.NONE); + lbl.setText(exception.getLocalizedMessage() + + (exception instanceof CmsException ? "" : "(" + + exception.getClass().getName() + ")") + "\n"); + lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + lbl.addMouseListener(this); + if (exception.getCause() != null) + appendCause(pane, exception.getCause()); + + StringBuilder mailToUrl = new StringBuilder("mailto:?"); + try { + mailToUrl.append("subject=").append( + URLEncoder.encode( + "Exception " + + new SimpleDateFormat("yyyy-MM-dd hh:mm") + .format(new Date()), "UTF-8") + .replace("+", "%20")); + + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + IOUtils.closeQuietly(sw); + + // see + // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character + String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace( + "+", "%20"); + mailToUrl.append("&body=").append(encoded); + } catch (UnsupportedEncodingException e) { + mailToUrl.append("&body=").append("Could not encode: ") + .append(e.getMessage()); + } + Label mailTo = new Label(pane, SWT.NONE); + CmsUtils.markup(mailTo); + mailTo.setText("Send details"); + mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + + pack(); + layout(); + + setLocation(source.toDisplay(source.getSize().x - getSize().x, + source.getSize().y - getSize().y)); + open(); + } + + private void appendCause(Composite parent, Throwable e) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(" caused by: " + e.getLocalizedMessage() + " (" + + e.getClass().getName() + ")" + "\n"); + lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + lbl.addMouseListener(this); + if (e.getCause() != null) + appendCause(parent, e.getCause()); + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + close(); + dispose(); + } + + @Override + public void mouseUp(MouseEvent e) { + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenu.java new file mode 100644 index 000000000..a654dddd4 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenu.java @@ -0,0 +1,48 @@ +package org.argeo.cms.util; + +import org.argeo.cms.CmsException; +import org.argeo.cms.widgets.auth.CmsLoginShell; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The site-related user menu */ +public class UserMenu extends CmsLoginShell { + private final Control source; + + public UserMenu(Control source) { + super(CmsUtils.getCmsView()); + if (source == null) + throw new CmsException("Source control cannot be null."); + this.source = source; + open(); + } + + @Override + protected Shell createShell() { + return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER + | SWT.ON_TOP); + } + + @Override + public void open() { + Shell shell = getShell(); + shell.pack(); + shell.layout(); + shell.setLocation(source.toDisplay(source.getSize().x + - shell.getSize().x, source.getSize().y)); + shell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + closeShell(); + } + }); + super.open(); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenuLink.java b/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenuLink.java new file mode 100644 index 000000000..1b7ca03f4 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/UserMenuLink.java @@ -0,0 +1,84 @@ +package org.argeo.cms.util; + +import javax.jcr.Node; +import javax.security.auth.Subject; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.CmsStyles; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.cms.auth.CurrentUser; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +/** Open the user menu when clicked */ +public class UserMenuLink extends MenuLink { + + public UserMenuLink() { + setCustom(CmsStyles.CMS_USER_MENU_LINK); + } + + @Override + public Control createUi(Composite parent, Node context) { + Subject subject = CmsUtils.getCmsView().getSubject(); + String username = CurrentUser.getUsername(subject); + if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS)) + setLabel(CmsMsg.login.lead()); + else { + setLabel(CurrentUser.getDisplayName(subject)); + } + Label link = (Label) ((Composite) super.createUi(parent, context)) + .getChildren()[0]; + link.addMouseListener(new UserMenuLinkController()); + return link.getParent(); + } + + protected UserMenu createUserMenu(Control source) { + return new UserMenu(source.getParent()); + } + + private class UserMenuLinkController implements MouseListener, + DisposeListener { + private static final long serialVersionUID = 3634864186295639792L; + + private UserMenu userMenu = null; + private long lastDisposeTS = 0l; + + // + // MOUSE LISTENER + // + @Override + public void mouseDown(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (userMenu == null) { + long durationSinceLastDispose = System.currentTimeMillis() + - lastDisposeTS; + // avoid to reopen the menu, if one has clicked gain + if (durationSinceLastDispose > 200) { + userMenu = createUserMenu(source); + userMenu.getShell().addDisposeListener(this); + } + } + } + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseUp(MouseEvent e) { + } + + @Override + public void widgetDisposed(DisposeEvent event) { + userMenu = null; + lastDisposeTS = System.currentTimeMillis(); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminUtils.java new file mode 100644 index 000000000..9ccc3057f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminUtils.java @@ -0,0 +1,242 @@ +package org.argeo.cms.util.useradmin; + +import java.security.AccessController; +import java.util.List; +import java.util.Set; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsView; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.util.CmsUtils; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.jcr.JcrUtils; +import org.argeo.osgi.useradmin.LdifName; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** Centralise common patterns to manage roles with a user admin */ +public class UserAdminUtils { + + /** Retrieves a {@link Role} given a LDAP name */ + public final static Role getRole(UserAdmin userAdmin, LdapName dn) { + Role role = userAdmin.getRole(dn.toString()); + return role; + } + + /** Retrieves the unique local username given a {@link User}. */ + public final static String getUsername(User user) { + String username = null; + if (user instanceof Group) + username = getProperty(user, LdifName.cn.name()); + else + username = getProperty(user, LdifName.uid.name()); + return username; + } + + /** + * Easily retrieves one of the {@link Role}'s property or an empty String if + * the requested property is not defined + */ + public final static String getProperty(Role role, String key) { + Object obj = role.getProperties().get(key); + if (obj != null) + return (String) obj; + else + return ""; + } + + // CENTRALIZE SOME METHODS UNTIL API IS STABLE + /** Simply checks if current user is registered */ + public static boolean isRegistered() { + return !CurrentUser.isAnonymous(); + } + + /** Simply checks if current user as a home */ + public static boolean hasHome() { + return isRegistered(); + } + + // SELF HELPERS + /** Simply retrieves the current logged-in user display name. */ + public static User getCurrentUser(UserAdmin userAdmin) { + return (User) getRole(userAdmin, getCurrentUserLdapName()); + } + + /** Simply retrieves the current logged-in user display name. */ + public static String getCurrentUserDisplayName(UserAdmin userAdmin) { + String username = getCurrentUsername(); + return getUserDisplayName(userAdmin, username); + } + + /** Simply retrieves the current logged-in user display name. */ + public static String getCurrentUserMail(UserAdmin userAdmin) { + String username = getCurrentUsername(); + return getUserMail(userAdmin, username); + } + + /** Returns the local name of the current connected user */ + public final static String getUsername(UserAdmin userAdmin) { + LdapName dn = getCurrentUserLdapName(); + return getUsername((User) getRole(userAdmin, dn)); + } + + /** Returns true if the current user is in the specified role */ + public static boolean isUserInRole(String role) { + Set roles = CurrentUser.roles(); + return roles.contains(role); + } + + /** Simply checks if current user is the same as the passed one */ + public static boolean isCurrentUser(User user) { + String userName = getProperty(user, LdifName.dn.name()); + try { + LdapName selfUserName = getCurrentUserLdapName(); + LdapName userLdapName = new LdapName(userName); + if (userLdapName.equals(selfUserName)) + return true; + else + return false; + } catch (InvalidNameException e) { + throw new CmsException("User " + user + " has an unvalid dn: " + + userName, e); + } + } + + public final static LdapName getCurrentUserLdapName() { + String name = getCurrentUsername(); + return getLdapName(name); + } + + /** Simply retrieves username for current user, generally a LDAP dn */ + public static String getCurrentUsername() { + Subject subject = currentSubject(); + String name = subject.getPrincipals(X500Principal.class).iterator() + .next().toString(); + return name; + } + + /** + * Fork of the {@link CurrentUser#currentSubject} method that is private. + * TODO Enhance and factorize + */ + private static Subject currentSubject() { + CmsView cmsView = CmsUtils.getCmsView(); + if (cmsView != null) + return cmsView.getSubject(); + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) + return subject; + throw new RuntimeException("Cannot find related subject"); + } + + // HOME MANAGEMENT + /** + * Simply retrieves the *relative* path to the current user home node from + * the base home node + */ + public static String getCurrentUserHomeRelPath() { + return getHomeRelPath(getCurrentUsername()); + } + + /** + * Simply retrieves the *relative* path to the home node of a user given its + * userName + */ + public static String getHomeRelPath(String userName) { + String id = getUserUid(userName); + String currHomePath = JcrUtils.firstCharsToPath(id, 2) + "/" + id; + return currHomePath; + } + + // HELPERS TO RETRIEVE REMARKABLE PROPERTIES + /** Simply retrieves the user uid from his dn with no useradmin */ + public static String getUserUid(String dn) { + LdapName ldapName = getLdapName(dn); + Rdn last = ldapName.getRdn(ldapName.size() - 1); + if (last.getType().toLowerCase().equals(LdifName.uid.name()) + || last.getType().toLowerCase().equals(LdifName.cn.name())) + return (String) last.getValue(); + else + throw new CmsException("Cannot retrieve user uid, " + + "non valid dn: " + dn); + } + + /** + * Returns the local username if no user with this dn is found or if the + * found user has no defined display name + */ + public static String getUserDisplayName(UserAdmin userAdmin, String dn) { + Role user = getRole(userAdmin, getLdapName(dn)); + if (user == null) + return getUserUid(dn); + String displayName = getProperty(user, LdifName.displayName.name()); + if (EclipseUiUtils.isEmpty(displayName)) + displayName = getProperty(user, LdifName.cn.name()); + if (EclipseUiUtils.isEmpty(displayName)) + return getUserUid(dn); + else + return displayName; + } + + /** + * Returns null if no user with this dn is found or if the found user has no + * defined mail + */ + public static String getUserMail(UserAdmin userAdmin, String dn) { + Role user = getRole(userAdmin, getLdapName(dn)); + if (user == null) + return null; + else + return getProperty(user, LdifName.mail.name()); + } + + // VARIOUS UI HELPERS + public final static String buildDefaultCn(String firstName, String lastName) { + return (firstName.trim() + " " + lastName.trim() + " ").trim(); + } + + /** Simply retrieves a display name of the relevant domain */ + public final static String getDomainName(User user) { + String dn = user.getName(); + if (dn.endsWith(AuthConstants.ROLES_BASEDN)) + return "System roles"; + try { + LdapName name = new LdapName(dn); + List rdns = name.getRdns(); + String dname = null; + int i = 0; + loop: while (i < rdns.size()) { + Rdn currrRdn = rdns.get(i); + if (!LdifName.dc.name().equals(currrRdn.getType())) + break loop; + else { + String currVal = (String) currrRdn.getValue(); + dname = dname == null ? currVal : currVal + "." + dname; + } + i++; + } + return dname; + } catch (InvalidNameException e) { + throw new CmsException("Unable to get domain name for " + dn, e); + } + } + + // Local Helpers + /** Simply retrieves a LDAP name from a dn with no exception */ + public static LdapName getLdapName(String dn) { + try { + return new LdapName(dn); + } catch (InvalidNameException e) { + throw new CmsException("Cannot parse LDAP name " + dn, e); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java b/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java new file mode 100644 index 000000000..aa764d57c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java @@ -0,0 +1,105 @@ +package org.argeo.cms.util.useradmin; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.osgi.framework.ServiceReference; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** + * Base useradmin wrapper. Implementing application might extends to add + * business specific behaviour + */ +public abstract class UserAdminWrapper { + // private Log log = LogFactory.getLog(UserAdminWrapper.class); + + private UserAdmin userAdmin; + private ServiceReference userAdminServiceReference; + private UserTransaction userTransaction; + + /* USER ADMIN LISTENER MANAGEMENT */ + List listeners = new ArrayList(); + + // TODO implement safer mechanism + public void addListener(UserAdminListener userAdminListener) { + if (!listeners.contains(userAdminListener)) + listeners.add(userAdminListener); + } + + /** + * Starts a transaction if none already exists and notify the userAdmin + * listeners.Must be called from the UI Thread. + */ + public UserTransaction beginTransactionIfNeeded() { + try { + if (userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION) { + userTransaction.begin(); + } + return userTransaction; + } catch (Exception e) { + throw new CmsException("Unable to begin transaction", e); + } + } + + // Expose this? + public void removeListener(UserAdminListener userAdminListener) { + if (listeners.contains(userAdminListener)) + listeners.remove(userAdminListener); + } + + public void notifyListeners(UserAdminEvent event) { + for (UserAdminListener listener : listeners) + listener.roleChanged(event); + } + + public Map getKnownBaseDns(boolean onlyWritable) { + Map dns = new HashMap(); + for (String uri : userAdminServiceReference.getPropertyKeys()) { + if (!uri.startsWith("/")) + continue; + Dictionary props = UserAdminConf.uriAsProperties(uri); + String readOnly = UserAdminConf.readOnly.getValue(props); + String baseDn = UserAdminConf.baseDn.getValue(props); + + if (onlyWritable && "true".equals(readOnly)) + continue; + if (baseDn.equalsIgnoreCase(AuthConstants.ROLES_BASEDN)) + continue; + dns.put(baseDn, uri); + } + return dns; + } + + public UserAdmin getUserAdmin() { + return userAdmin; + } + + public UserTransaction getUserTransaction() { + return userTransaction; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdmin(UserAdmin userAdmin) { + this.userAdmin = userAdmin; + } + + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + public void setUserAdminServiceReference( + ServiceReference userAdminServiceReference) { + this.userAdminServiceReference = userAdminServiceReference; + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/AbstractPageViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/AbstractPageViewer.java new file mode 100644 index 000000000..b52f76be5 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/AbstractPageViewer.java @@ -0,0 +1,309 @@ +package org.argeo.cms.viewers; + +import java.util.Observable; +import java.util.Observer; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.widgets.ScrolledPage; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.SAXParseException; + +/** Base class for viewers related to a page */ +public abstract class AbstractPageViewer extends ContentViewer implements + Observer { + private static final long serialVersionUID = 5438688173410341485L; + + private final static Log log = LogFactory.getLog(AbstractPageViewer.class); + + private final boolean readOnly; + /** The basis for the layouts, typically a ScrolledPage. */ + private final Composite page; + private final CmsEditable cmsEditable; + + private MouseListener mouseListener; + private FocusListener focusListener; + + private EditablePart edited; + private ISelection selection = StructuredSelection.EMPTY; + + protected AbstractPageViewer(Section parent, int style, + CmsEditable cmsEditable) { + // read only at UI level + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + + this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE + : cmsEditable; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + + if (cmsEditable.canEdit()) { + mouseListener = createMouseListener(); + focusListener = createFocusListener(); + } + page = findPage(parent); + } + + /** + * Can be called to simplify the called to isModelInitialized() and + * initModel() + */ + protected void initModelIfNeeded(Node node) { + try { + if (!isModelInitialized(node)) + if (getCmsEditable().canEdit()) { + initModel(node); + node.getSession().save(); + } + } catch (Exception e) { + throw new CmsException("Cannot initialize model", e); + } + } + + /** Called if user can edit and model is not initialized */ + protected Boolean isModelInitialized(Node node) throws RepositoryException { + return true; + } + + /** Called if user can edit and model is not initialized */ + protected void initModel(Node node) throws RepositoryException { + } + + /** Create (retrieve) the MouseListener to use. */ + protected MouseListener createMouseListener() { + return new MouseAdapter() { + private static final long serialVersionUID = 1L; + }; + } + + /** Create (retrieve) the FocusListener to use. */ + protected FocusListener createFocusListener() { + return new FocusListener() { + private static final long serialVersionUID = 1L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + } + }; + } + + protected Composite findPage(Composite composite) { + if (composite instanceof ScrolledPage) { + return (ScrolledPage) composite; + } else { + if (composite.getParent() == null) + return composite; + return findPage(composite.getParent()); + } + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) + editingStateChanged(cmsEditable); + } + + /** To be overridden in order to provide the actual refresh */ + protected void refresh(Control control) throws RepositoryException { + } + + /** To be overridden.Save the edited part. */ + protected void save(EditablePart part) throws RepositoryException { + } + + /** Prepare the edited part */ + protected void prepare(EditablePart part, Object caretPosition) { + } + + /** Notified when the editing state changed. Does nothing, to be overridden */ + protected void editingStateChanged(CmsEditable cmsEditable) { + } + + @Override + public void refresh() { + try { + if (cmsEditable.canEdit() && !readOnly) + mouseListener = createMouseListener(); + else + mouseListener = null; + refresh(getControl()); + layout(getControl()); + } catch (RepositoryException e) { + throw new CmsException("Cannot refresh", e); + } + } + + @Override + public void setSelection(ISelection selection, boolean reveal) { + this.selection = selection; + } + + protected void updateContent(EditablePart part) throws RepositoryException { + } + + // LOW LEVEL EDITION + protected void edit(EditablePart part, Object caretPosition) { + try { + if (edited == part) + return; + + if (edited != null && edited != part) { + EditablePart previouslyEdited = edited; + try { + stopEditing(true); + } catch (Exception e) { + notifyEditionException(e); + edit(previouslyEdited, caretPosition); + return; + } + } + + part.startEditing(); + updateContent(part); + prepare(part, caretPosition); + edited = part; + layout(part.getControl()); + } catch (RepositoryException e) { + throw new CmsException("Cannot edit " + part, e); + } + } + + private void stopEditing(Boolean save) throws RepositoryException { + if (edited instanceof Widget && ((Widget) edited).isDisposed()) { + edited = null; + return; + } + + assert edited != null; + if (edited == null) { + if (log.isTraceEnabled()) + log.warn("Told to stop editing while not editing anything"); + return; + } + + if (save) + save(edited); + + edited.stopEditing(); + updateContent(edited); + layout(((EditablePart) edited).getControl()); + edited = null; + } + + // METHODS AVAILABLE TO EXTENDING CLASSES + protected void saveEdit() { + try { + if (edited != null) + stopEditing(true); + } catch (RepositoryException e) { + throw new CmsException("Cannot stop editing", e); + } + } + + protected void cancelEdit() { + try { + if (edited != null) + stopEditing(false); + } catch (RepositoryException e) { + throw new CmsException("Cannot cancel editing", e); + } + } + + /** Layout this controls from the related base page. */ + public void layout(Control... controls) { + page.layout(controls); + } + + /** + * Find the first {@link EditablePart} in the parents hierarchy of this + * control + */ + protected EditablePart findDataParent(Control parent) { + if (parent instanceof EditablePart) { + return (EditablePart) parent; + } + if (parent.getParent() != null) + return findDataParent(parent.getParent()); + else + throw new CmsException("No data parent found"); + } + + // UTILITIES + /** Check whether the edited part is in a proper state */ + protected void checkEdited() { + if (edited == null || (edited instanceof Widget) + && ((Widget) edited).isDisposed()) + throw new CmsException( + "Edited should not be null or disposed at this stage"); + } + + /** Persist all changes. */ + protected void persistChanges(Session session) throws RepositoryException { + session.save(); + session.refresh(false); + // TODO notify that changes have been persisted + } + + /** Convenience method using a Node in order to save the underlying session. */ + protected void persistChanges(Node anyNode) throws RepositoryException { + persistChanges(anyNode.getSession()); + } + + /** Notify edition exception */ + protected void notifyEditionException(Throwable e) { + Throwable eToLog = e; + if (e instanceof IllegalArgumentException) + if (e.getCause() instanceof SAXParseException) + eToLog = e.getCause(); + log.error(eToLog.getMessage()); + if (log.isTraceEnabled()) + log.trace("Full stack of " + eToLog.getMessage(), e); + // TODO Light error notification popup + } + + // GETTERS / SETTERS + public boolean isReadOnly() { + return readOnly; + } + + protected EditablePart getEdited() { + return edited; + } + + public MouseListener getMouseListener() { + return mouseListener; + } + + public FocusListener getFocusListener() { + return focusListener; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + + @Override + public ISelection getSelection() { + return selection; + } +} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/EditablePart.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/EditablePart.java new file mode 100644 index 000000000..99f8acf9a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/EditablePart.java @@ -0,0 +1,11 @@ +package org.argeo.cms.viewers; + +import org.eclipse.swt.widgets.Control; + +public interface EditablePart { + public void startEditing(); + + public void stopEditing(); + + public Control getControl(); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/ItemPart.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/ItemPart.java new file mode 100644 index 000000000..52e5a88eb --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/ItemPart.java @@ -0,0 +1,9 @@ +package org.argeo.cms.viewers; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +/** An editable part related to a JCR Item */ +public interface ItemPart { + public Item getItem() throws RepositoryException; +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java new file mode 100644 index 000000000..2d7daae59 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java @@ -0,0 +1,101 @@ +package org.argeo.cms.viewers; + +import java.util.Observable; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.version.VersionManager; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsEditionEvent; +import org.argeo.cms.CmsException; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** Provides the CmsEditable semantic based on JCR versioning. */ +public class JcrVersionCmsEditable extends Observable implements CmsEditable { + private final String nodePath;// cache + private final VersionManager versionManager; + private final Boolean canEdit; + + public JcrVersionCmsEditable(Node node) throws RepositoryException { + this.nodePath = node.getPath(); + if (node.getSession().hasPermission(node.getPath(), + Session.ACTION_SET_PROPERTY)) { + // was Session.ACTION_ADD_NODE + canEdit = true; + if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) { + node.addMixin(NodeType.MIX_VERSIONABLE); + node.getSession().save(); + } + versionManager = node.getSession().getWorkspace() + .getVersionManager(); + } else { + canEdit = false; + versionManager = null; + } + + // bind keys + if (canEdit) { + Display display = Display.getCurrent(); + display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN", + "CTRL+E" }); + display.addFilter(SWT.KeyDown, new Listener() { + private static final long serialVersionUID = -4378653870463187318L; + + public void handleEvent(Event e) { + boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0; + if (ctrlPressed && e.keyCode == '\r') + stopEditing(); + else if (ctrlPressed && e.keyCode == 'E') + stopEditing(); + } + }); + } + } + + @Override + public Boolean canEdit() { + return canEdit; + } + + public Boolean isEditing() { + try { + if (!canEdit()) + return false; + return versionManager.isCheckedOut(nodePath); + } catch (RepositoryException e) { + throw new CmsException("Cannot check whether " + nodePath + + " is editing", e); + } + } + + @Override + public void startEditing() { + try { + versionManager.checkout(nodePath); + setChanged(); + } catch (RepositoryException e1) { + throw new CmsException("Cannot publish " + nodePath); + } + notifyObservers(new CmsEditionEvent(nodePath, + CmsEditionEvent.START_EDITING)); + } + + @Override + public void stopEditing() { + try { + versionManager.checkin(nodePath); + setChanged(); + } catch (RepositoryException e1) { + throw new CmsException("Cannot publish " + nodePath, e1); + } + notifyObservers(new CmsEditionEvent(nodePath, + CmsEditionEvent.STOP_EDITING)); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/NodePart.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/NodePart.java new file mode 100644 index 000000000..db9a60a87 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/NodePart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.viewers; + +import javax.jcr.Node; + +/** An editable part related to a node */ +public interface NodePart extends ItemPart { + public Node getNode(); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/PropertyPart.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/PropertyPart.java new file mode 100644 index 000000000..50fdd0601 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/PropertyPart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.viewers; + +import javax.jcr.Property; + +/** An editable part related to a JCR Property */ +public interface PropertyPart extends ItemPart { + public Property getProperty(); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/Section.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/Section.java new file mode 100644 index 000000000..af7fd877f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/Section.java @@ -0,0 +1,155 @@ +package org.argeo.cms.viewers; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsNames; +import org.argeo.cms.util.CmsUtils; +import org.argeo.cms.widgets.JcrComposite; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +public class Section extends JcrComposite implements CmsNames { + private static final long serialVersionUID = -5933796173755739207L; + + private final Section parentSection; + private Composite sectionHeader; + private final Integer relativeDepth; + + public Section(Composite parent, int style, Node node) + throws RepositoryException { + this(parent, findSection(parent), style, node); + } + + public Section(Section section, int style, Node node) + throws RepositoryException { + this(section, section, style, node); + } + + protected Section(Composite parent, Section parentSection, int style, + Node node) throws RepositoryException { + super(parent, style, node); + this.parentSection = parentSection; + if (parentSection != null) { + relativeDepth = getNode().getDepth() + - parentSection.getNode().getDepth(); + } else { + relativeDepth = 0; + } + setLayout(CmsUtils.noSpaceGridLayout()); + } + + public Map getSubSections() throws RepositoryException { + LinkedHashMap result = new LinkedHashMap(); + for (Control child : getChildren()) { + if (child instanceof Composite) { + collectDirectSubSections((Composite) child, result); + } + } + return Collections.unmodifiableMap(result); + } + + private void collectDirectSubSections(Composite composite, + LinkedHashMap subSections) + throws RepositoryException { + if (composite == sectionHeader || composite instanceof EditablePart) + return; + if (composite instanceof Section) { + Section section = (Section) composite; + subSections.put(section.getNodeId(), section); + return; + } + + for (Control child : composite.getChildren()) + if (child instanceof Composite) + collectDirectSubSections((Composite) child, subSections); + } + + public void createHeader() { + if (sectionHeader != null) + throw new CmsException("Section header was already created"); + + sectionHeader = new Composite(this, SWT.NONE); + sectionHeader.setLayoutData(CmsUtils.fillWidth()); + sectionHeader.setLayout(CmsUtils.noSpaceGridLayout()); + // sectionHeader.moveAbove(null); + // layout(); + } + + public Composite getHeader() { + if (sectionHeader != null && sectionHeader.isDisposed()) + sectionHeader = null; + return sectionHeader; + } + + // SECTION PARTS + public SectionPart getSectionPart(String partId) { + for (Control child : getChildren()) { + if (child instanceof SectionPart) { + SectionPart paragraph = (SectionPart) child; + if (paragraph.getPartId().equals(partId)) + return paragraph; + } + } + return null; + } + + public SectionPart nextSectionPart(SectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) + if (i + 1 < children.length) { + Composite next = (Composite) children[i + 1]; + return (SectionPart) next; + } else { + // next section + } + } + return null; + } + + public SectionPart previousSectionPart(SectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) + if (i != 0) { + Composite previous = (Composite) children[i - 1]; + return (SectionPart) previous; + } else { + // previous section + } + } + return null; + } + + @Override + public String toString() { + if (parentSection == null) + return "Main section " + getNode(); + return "Section " + getNode(); + } + + public Section getParentSection() { + return parentSection; + } + + public Integer getRelativeDepth() { + return relativeDepth; + } + + /** Recursively finds the related section in the parents (can be itself) */ + public static Section findSection(Control control) { + if (control == null) + return null; + if (control instanceof Section) + return (Section) control; + else + return findSection(control.getParent()); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/viewers/SectionPart.java b/org.argeo.cms.ui/src/org/argeo/cms/viewers/SectionPart.java new file mode 100644 index 000000000..6cd45c5ba --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/viewers/SectionPart.java @@ -0,0 +1,9 @@ +package org.argeo.cms.viewers; + + +/** An editable part dynamically related to a Section */ +public interface SectionPart extends EditablePart, NodePart { + public String getPartId(); + + public Section getSection(); +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableImage.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableImage.java new file mode 100644 index 000000000..2e70eb89f --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableImage.java @@ -0,0 +1,112 @@ +package org.argeo.cms.widgets; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** A stylable and editable image. */ +public abstract class EditableImage extends StyledControl { + private static final long serialVersionUID = -5689145523114022890L; + private final static Log log = LogFactory.getLog(EditableImage.class); + + private Point preferredImageSize; + private Boolean loaded = false; + + public EditableImage(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + public EditableImage(Composite parent, int swtStyle, + Point preferredImageSize) { + super(parent, swtStyle); + this.preferredImageSize = preferredImageSize; + } + + public EditableImage(Composite parent, int style, Node node, + boolean cacheImmediately, Point preferredImageSize) + throws RepositoryException { + super(parent, style, node, cacheImmediately); + this.preferredImageSize = preferredImageSize; + } + + @Override + protected void setContainerLayoutData(Composite composite) { + // composite.setLayoutData(fillWidth()); + } + + @Override + protected void setControlLayoutData(Control control) { + // control.setLayoutData(fillWidth()); + } + + /** To be overriden. */ + protected String createImgTag() throws RepositoryException { + return CmsUtils.noImg(preferredImageSize != null ? preferredImageSize + : getSize()); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle()); + // lbl.setLayoutData(CmsUtils.fillWidth()); + CmsUtils.markup(lbl); + CmsUtils.style(lbl, style); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + load(lbl); + return lbl; + } + + /** To be overriden. */ + protected synchronized Boolean load(Control control) { + String imgTag; + try { + imgTag = createImgTag(); + } catch (Exception e) { + // throw new CmsException("Cannot retrieve image", e); + log.error("Cannot retrieve image", e); + imgTag = CmsUtils.noImg(preferredImageSize); + loaded = false; + } + + if (imgTag == null) { + loaded = false; + imgTag = CmsUtils.noImg(preferredImageSize); + } else + loaded = true; + if (control != null) { + ((Label) control).setText(imgTag); + control.setSize(preferredImageSize != null ? preferredImageSize + : getSize()); + } else { + loaded = false; + } + getParent().layout(); + return loaded; + } + + public void setPreferredSize(Point size) { + this.preferredImageSize = size; + if (!loaded) { + load((Label) getControl()); + } + } + + protected Text createText(Composite box, String style) { + Text text = new Text(box, getStyle()); + CmsUtils.style(text, style); + return text; + } + + public Point getPreferredImageSize() { + return preferredImageSize; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableText.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableText.java new file mode 100644 index 000000000..e7c56ea72 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/EditableText.java @@ -0,0 +1,76 @@ +package org.argeo.cms.widgets; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable text part displaying styled text. */ +public class EditableText extends StyledControl { + private static final long serialVersionUID = -6372283442330912755L; + + public EditableText(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + public EditableText(Composite parent, int style, Item item) + throws RepositoryException { + this(parent, style, item, false); + } + + public EditableText(Composite parent, int style, Item item, + boolean cacheImmediately) throws RepositoryException { + super(parent, style, item, cacheImmediately); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) + return createText(box, style); + else + return createLabel(box, style); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle() | SWT.WRAP); + lbl.setLayoutData(CmsUtils.fillWidth()); + CmsUtils.style(lbl, style); + CmsUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + protected Text createText(Composite box, String style) { + final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP); + GridData textLayoutData = CmsUtils.fillWidth(); + // textLayoutData.heightHint = preferredHeight; + text.setLayoutData(textLayoutData); + CmsUtils.style(text, style); + text.setFocus(); + return text; + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) + ((Label) child).setText(text); + else if (child instanceof Text) + ((Text) child).setText(text); + } + + public Text getAsText() { + return (Text) getControl(); + } + + public Label getAsLabel() { + return (Label) getControl(); + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/JcrComposite.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/JcrComposite.java new file mode 100644 index 000000000..358b45315 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/JcrComposite.java @@ -0,0 +1,175 @@ +package org.argeo.cms.widgets; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.cms.CmsException; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +/** A composite which can (optionally) manage a JCR Item. */ +public class JcrComposite extends Composite { + private static final long serialVersionUID = -1447009015451153367L; + + private final Session session; + + private String nodeId; + private String property = null; + private Node cache; + + /** Regular composite constructor. No layout is set. */ + public JcrComposite(Composite parent, int style) { + super(parent, style); + session = null; + nodeId = null; + } + + public JcrComposite(Composite parent, int style, Item item) + throws RepositoryException { + this(parent, style, item, false); + } + + public JcrComposite(Composite parent, int style, Item item, + boolean cacheImmediately) throws RepositoryException { + super(parent, style); + this.session = item.getSession(); + if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) { + // (useless?) optimization: we only save a pointer to the session, + // not even a reference to the item + this.nodeId = null; + } else { + Node node; + Property property = null; + if (item instanceof Node) { + node = (Node) item; + } else {// Property + property = (Property) item; + if (property.isMultiple())// TODO manage property index + throw new CmsException( + "Multiple properties not supported yet."); + this.property = property.getName(); + node = property.getParent(); + } + this.nodeId = node.getIdentifier(); + if (cacheImmediately) + this.cache = node; + } + setLayout(CmsUtils.noSpaceGridLayout()); + } + + public synchronized Node getNode() { + try { + if (!itemIsNode()) + throw new CmsException("Item is not a Node"); + return getNodeInternal(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get node " + nodeId, e); + } + } + + private synchronized Node getNodeInternal() throws RepositoryException { + if (cache != null) + return cache; + else if (session != null) + if (nodeId != null) + return session.getNodeByIdentifier(nodeId); + else + return null; + else + return null; + } + + public synchronized Property getProperty() { + try { + if (itemIsNode()) + throw new CmsException("Item is not a Property"); + Node node = getNodeInternal(); + if (!node.hasProperty(property)) + throw new CmsException("Property " + property + + " is not set on " + node); + return node.getProperty(property); + } catch (RepositoryException e) { + throw new CmsException("Cannot get property " + property + + " from node " + nodeId, e); + } + } + + public synchronized Boolean itemIsNode() { + return property == null; + } + + /** Set/update the cache or change the node */ + public synchronized void setNode(Node node) throws RepositoryException { + if (!itemIsNode()) + throw new CmsException("Cannot set a Node on a Property"); + + if (node == null) {// clear cache + this.cache = null; + return; + } + + if (session == null || session != node.getSession())// check session + throw new CmsException("Uncompatible session"); + + if (nodeId == null || !nodeId.equals(node.getIdentifier())) { + nodeId = node.getIdentifier(); + cache = node; + itemUpdated(); + } else { + cache = node;// set/update cache + } + } + + /** Set/update the cache or change the property */ + public synchronized void setProperty(Property prop) + throws RepositoryException { + if (itemIsNode()) + throw new CmsException("Cannot set a Property on a Node"); + + if (prop == null) {// clear cache + this.cache = null; + return; + } + + if (session == null || session != prop.getSession())// check session + throw new CmsException("Uncompatible session"); + + Node node = prop.getNode(); + if (nodeId == null || !nodeId.equals(node.getIdentifier()) + || !property.equals(prop.getName())) { + nodeId = node.getIdentifier(); + property = prop.getName(); + cache = node; + itemUpdated(); + } else { + cache = node;// set/update cache + } + } + + public synchronized String getNodeId() { + return nodeId; + } + + /** Change the node, does nothing if same. */ + public synchronized void setNodeId(String nodeId) + throws RepositoryException { + if (this.nodeId != null && this.nodeId.equals(nodeId)) + return; + this.nodeId = nodeId; + if (cache != null) + cache = session.getNodeByIdentifier(this.nodeId); + itemUpdated(); + } + + protected synchronized void itemUpdated() { + layout(); + } + + public Session getSession() { + return session; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/ScrolledPage.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/ScrolledPage.java new file mode 100644 index 000000000..c36ed2052 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/ScrolledPage.java @@ -0,0 +1,60 @@ +package org.argeo.cms.widgets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; + +/** + * A composite that can be scrolled vertically. It wraps a + * {@link ScrolledComposite} (and is being wrapped by it), simplifying its + * configuration. + */ +public class ScrolledPage extends Composite { + private static final long serialVersionUID = 1593536965663574437L; + + private ScrolledComposite scrolledComposite; + + public ScrolledPage(Composite parent, int style) { + super(new ScrolledComposite(parent, SWT.V_SCROLL), style); + scrolledComposite = (ScrolledComposite) getParent(); + scrolledComposite.setContent(this); + + scrolledComposite.setExpandVertical(true); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.addControlListener(new ScrollControlListener()); + } + + @Override + public void layout(boolean changed, boolean all) { + updateScroll(); + super.layout(changed, all); + } + + protected void updateScroll() { + Rectangle r = scrolledComposite.getClientArea(); + Point preferredSize = computeSize(r.width, SWT.DEFAULT); + scrolledComposite.setMinHeight(preferredSize.y); + } + + // public ScrolledComposite getScrolledComposite() { + // return this.scrolledComposite; + // } + + /** Set it on the wrapping scrolled composite */ + @Override + public void setLayoutData(Object layoutData) { + scrolledComposite.setLayoutData(layoutData); + } + + private class ScrollControlListener extends + org.eclipse.swt.events.ControlAdapter { + private static final long serialVersionUID = -3586986238567483316L; + + public void controlResized(ControlEvent e) { + updateScroll(); + } + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/StyledControl.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/StyledControl.java new file mode 100644 index 000000000..ec1dbe77e --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/StyledControl.java @@ -0,0 +1,138 @@ +package org.argeo.cms.widgets; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsConstants; +import org.argeo.cms.CmsNames; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Editable text part displaying styled text. */ +public abstract class StyledControl extends JcrComposite implements + CmsConstants, CmsNames { + private static final long serialVersionUID = -6372283442330912755L; + private Control control; + + private Composite container; + private Composite box; + + protected MouseListener mouseListener; + protected FocusListener focusListener; + + private Boolean editing = Boolean.FALSE; + + public StyledControl(Composite parent, int swtStyle) { + super(parent, swtStyle); + setLayout(CmsUtils.noSpaceGridLayout()); + } + + public StyledControl(Composite parent, int style, Item item) + throws RepositoryException { + super(parent, style, item); + } + + public StyledControl(Composite parent, int style, Item item, + boolean cacheImmediately) throws RepositoryException { + super(parent, style, item, cacheImmediately); + } + + protected abstract Control createControl(Composite box, String style); + + protected Composite createBox(Composite parent) { + Composite box = new Composite(parent, SWT.INHERIT_DEFAULT); + setContainerLayoutData(box); + box.setLayout(CmsUtils.noSpaceGridLayout()); + // new Label(box, SWT.NONE).setText("BOX"); + return box; + } + + public Control getControl() { + return control; + } + + protected synchronized Boolean isEditing() { + return editing; + } + + public synchronized void startEditing() { + assert !isEditing(); + editing = true; + // int height = control.getSize().y; + String style = (String) control.getData(STYLE); + clear(false); + control = createControl(box, style); + setControlLayoutData(control); + + // add the focus listener to the newly created edition control + if (focusListener != null) + control.addFocusListener(focusListener); + } + + public synchronized void stopEditing() { + assert isEditing(); + editing = false; + String style = (String) control.getData(STYLE); + clear(false); + control = createControl(box, style); + setControlLayoutData(control); + } + + public void setStyle(String style) { + Object currentStyle = null; + if (control != null) + currentStyle = control.getData(STYLE); + if (currentStyle != null && currentStyle.equals(style)) + return; + + // Integer preferredHeight = control != null ? control.getSize().y : + // null; + clear(true); + control = createControl(box, style); + setControlLayoutData(control); + + control.getParent().setData(STYLE, style + "_box"); + control.getParent().getParent().setData(STYLE, style + "_container"); + } + + /** To be overridden */ + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsUtils.fillWidth()); + } + + /** To be overridden */ + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsUtils.fillWidth()); + } + + protected void clear(boolean deep) { + if (deep) { + for (Control control : getChildren()) + control.dispose(); + container = createBox(this); + box = createBox(container); + } else { + control.dispose(); + } + } + + public void setMouseListener(MouseListener mouseListener) { + if (this.mouseListener != null && control != null) + control.removeMouseListener(this.mouseListener); + this.mouseListener = mouseListener; + if (control != null && this.mouseListener != null) + control.addMouseListener(mouseListener); + } + + public void setFocusListener(FocusListener focusListener) { + if (this.focusListener != null && control != null) + control.removeFocusListener(this.focusListener); + this.focusListener = focusListener; + if (control != null && this.focusListener != null) + control.addFocusListener(focusListener); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java new file mode 100644 index 000000000..b86fcb0b0 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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. + */ +package org.argeo.cms.widgets.auth; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.operation.ModalContext; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.osgi.framework.FrameworkUtil; + +/** Base for login dialogs */ +public abstract class AbstractLoginDialog extends TrayDialog implements CallbackHandler { + private static final long serialVersionUID = -8046708963512717709L; + + private final static Log log = LogFactory.getLog(AbstractLoginDialog.class); + + private Thread modalContextThread = null; + boolean processCallbacks = false; + boolean isCancelled = false; + Callback[] callbackArray; + + protected final Callback[] getCallbacks() { + return this.callbackArray; + } + + public abstract void internalHandle(); + + public boolean isCancelled() { + return isCancelled; + } + + protected AbstractLoginDialog(Shell parentShell) { + super(parentShell); + } + + /* + * (non-Javadoc) + * + * @see + * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth + * .callback.Callback[]) + */ + public void handle(final Callback[] callbacks) throws IOException { + // clean previous usage + if (processCallbacks) { + // this handler was already used + processCallbacks = false; + } + + if (modalContextThread != null) { + try { + modalContextThread.join(1000); + } catch (InterruptedException e) { + // silent + } + modalContextThread = null; + } + + // initialize + this.callbackArray = callbacks; + final Display display = Display.getDefault(); + display.syncExec(new Runnable() { + + public void run() { + isCancelled = false; + setBlockOnOpen(false); + open(); + + final Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setText("Login"); + okButton.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = -200281625679096775L; + + public void widgetSelected(final SelectionEvent event) { + processCallbacks = true; + } + + public void widgetDefaultSelected(final SelectionEvent event) { + // nothing to do + } + }); + final Button cancel = getButton(IDialogConstants.CANCEL_ID); + cancel.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = -3826030278084915815L; + + public void widgetSelected(final SelectionEvent event) { + isCancelled = true; + processCallbacks = true; + } + + public void widgetDefaultSelected(final SelectionEvent event) { + // nothing to do + } + }); + } + }); + try { + ModalContext.setAllowReadAndDispatch(true); // Works for now. + ModalContext.run(new IRunnableWithProgress() { + + public void run(final IProgressMonitor monitor) { + modalContextThread = Thread.currentThread(); + // Wait here until OK or cancel is pressed, then let it rip. + // The event + // listener + // is responsible for closing the dialog (in the + // loginSucceeded + // event). + while (!processCallbacks && (modalContextThread != null) + && (modalContextThread == Thread.currentThread()) + && FrameworkUtil.getBundle(AbstractLoginDialog.class).getBundleContext() != null) { + // Note: SecurityUiPlugin.getDefault() != null is false + // when the OSGi runtime is shut down + try { + Thread.sleep(100); + // if (display.isDisposed()) { + // log.warn("Display is disposed, killing login + // dialog thread"); + // throw new ThreadDeath(); + // } + } catch (final Exception e) { + // do nothing + } + } + processCallbacks = false; + // Call the adapter to handle the callbacks + if (!isCancelled()) + internalHandle(); + else + // clear callbacks are when cancelling + for (Callback callback : callbacks) + if (callback instanceof PasswordCallback) { + char[] arr = ((PasswordCallback) callback).getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + ((PasswordCallback) callback).setPassword(null); + } + } else if (callback instanceof NameCallback) + ((NameCallback) callback).setName(null); + } + }, true, new NullProgressMonitor(), Display.getDefault()); + } catch (ThreadDeath e) { + isCancelled = true; + log.debug("Thread " + Thread.currentThread().getId() + " died"); + throw e; + } catch (Exception e) { + isCancelled = true; + IOException ioe = new IOException("Unexpected issue in login dialog, see root cause for more details"); + ioe.initCause(e); + throw ioe; + } finally { + // so that the modal thread dies + processCallbacks = true; + // try { + // // wait for the modal context thread to gracefully exit + // modalContextThread.join(); + // } catch (InterruptedException ie) { + // // silent + // } + modalContextThread = null; + } + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText("Authentication"); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLogin.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLogin.java new file mode 100644 index 000000000..21f101aea --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLogin.java @@ -0,0 +1,296 @@ +package org.argeo.cms.widgets.auth; + +import static org.argeo.cms.CmsMsg.password; +import static org.argeo.cms.CmsMsg.username; +import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_ANONYMOUS; +import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_USER; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.LanguageCallback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.CmsStyles; +import org.argeo.cms.CmsView; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.HttpRequestCallback; +import org.argeo.cms.i18n.LocaleUtils; +import org.argeo.cms.ui.internal.Activator; +import org.argeo.cms.util.CmsUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class CmsLogin implements CmsStyles, CallbackHandler { + private final static Log log = LogFactory.getLog(CmsLogin.class); + + private Composite parent; + private Text usernameT, passwordT; + private Composite credentialsBlock; + private final SelectionListener loginSelectionListener; + + private final Locale defaultLocale; + private LocaleChoice localeChoice = null; + + private final CmsView cmsView; + + public CmsLogin(CmsView cmsView) { + this.cmsView = cmsView; + defaultLocale = Activator.getNodeState().getDefaultLocale(); + List locales = Activator.getNodeState().getLocales(); + if (locales != null) + localeChoice = new LocaleChoice(locales, defaultLocale); + loginSelectionListener = new SelectionListener() { + private static final long serialVersionUID = -8832133363830973578L; + + @Override + public void widgetSelected(SelectionEvent e) { + login(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }; + } + + protected boolean isAnonymous() { + return CurrentUser.isAnonymous(cmsView.getSubject()); + } + + public final void createUi(Composite parent) { + this.parent = parent; + createContents(parent); + } + + protected void createContents(Composite parent) { + defaultCreateContents(parent); + } + + public final void defaultCreateContents(Composite parent) { + parent.setLayout(CmsUtils.noSpaceGridLayout()); + Composite credentialsBlock = createCredentialsBlock(parent); + if (parent instanceof Shell) { + credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + } + } + + public final Composite createCredentialsBlock(Composite parent) { + if (isAnonymous()) { + return anonymousUi(parent); + } else { + return userUi(parent); + } + } + + protected Composite getCredentialsBlock() { + return credentialsBlock; + } + + protected Composite userUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + credentialsBlock.setLayoutData(CmsUtils.fillAll()); + + specificUserUi(credentialsBlock); + + Label l = new Label(credentialsBlock, SWT.NONE); + l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM); + l.setText(CmsMsg.logout.lead(locale)); + GridData lData = CmsUtils.fillWidth(); + lData.widthHint = 120; + l.setLayoutData(lData); + + l.addMouseListener(new MouseAdapter() { + private static final long serialVersionUID = 6444395812777413116L; + + public void mouseDown(MouseEvent e) { + logout(); + } + }); + return credentialsBlock; + } + + /** To be overridden */ + protected void specificUserUi(Composite parent) { + + } + + protected Composite anonymousUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + // We need a composite for the traversal + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + credentialsBlock.setLayoutData(CmsUtils.fillAll()); + + Integer textWidth = 120; + parent.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); + + // new Label(this, SWT.NONE).setText(CmsMsg.username.lead()); + usernameT = new Text(credentialsBlock, SWT.BORDER); + usernameT.setMessage(username.lead(locale)); + usernameT.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_USERNAME); + GridData gd = CmsUtils.fillWidth(); + gd.widthHint = textWidth; + usernameT.setLayoutData(gd); + + // new Label(this, SWT.NONE).setText(CmsMsg.password.lead()); + passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD); + passwordT.setMessage(password.lead(locale)); + passwordT.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_PASSWORD); + gd = CmsUtils.fillWidth(); + gd.widthHint = textWidth; + passwordT.setLayoutData(gd); + + TraverseListener tl = new TraverseListener() { + private static final long serialVersionUID = -1158892811534971856L; + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) + login(); + } + }; + credentialsBlock.addTraverseListener(tl); + usernameT.addTraverseListener(tl); + passwordT.addTraverseListener(tl); + parent.setTabList(new Control[] { credentialsBlock }); + credentialsBlock.setTabList(new Control[] { usernameT, passwordT }); + // credentialsBlock.setFocus(); + + extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener); + if (localeChoice != null) + createLocalesBlock(credentialsBlock); + return credentialsBlock; + } + + /** + * To be overridden in order to provide custome login button and other + * links. + */ + protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale, + SelectionListener loginSelectionListener) { + + } + + protected void updateLocale(Locale selectedLocale) { + // save already entered values + String usernameStr = usernameT.getText(); + char[] pwd = passwordT.getTextChars(); + + for (Control child : parent.getChildren()) + child.dispose(); + createContents(parent); + if (parent.getParent() != null) + parent.getParent().layout(); + else + parent.layout(); + usernameT.setText(usernameStr); + passwordT.setTextChars(pwd); + } + + protected Composite createLocalesBlock(final Composite parent) { + Composite c = new Composite(parent, SWT.NONE); + c.setLayout(CmsUtils.noSpaceGridLayout()); + c.setLayoutData(CmsUtils.fillAll()); + + SelectionListener selectionListener = new SelectionAdapter() { + private static final long serialVersionUID = 4891637813567806762L; + + public void widgetSelected(SelectionEvent event) { + Button button = (Button) event.widget; + if (button.getSelection()) { + localeChoice.setSelectedIndex((Integer) event.widget.getData()); + updateLocale(localeChoice.getSelectedLocale()); + } + }; + }; + + List locales = localeChoice.getLocales(); + for (Integer i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + Button button = new Button(c, SWT.RADIO); + button.setData(i); + button.setText(LocaleUtils.lead(locale.getDisplayName(locale), locale) + " (" + locale + ")"); + // button.addListener(SWT.Selection, listener); + button.addSelectionListener(selectionListener); + if (i == localeChoice.getSelectedIndex()) + button.setSelection(true); + } + return c; + } + + protected boolean login() { + Subject subject = cmsView.getSubject(); + LoginContext loginContext; + try { + // + // LOGIN + // + new LoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).logout(); + loginContext = new LoginContext(LOGIN_CONTEXT_USER, subject, this); + loginContext.login(); + } catch (FailedLoginException e) { + log.warn(e.getMessage()); + try { + Thread.sleep(3000); + } catch (InterruptedException e2) { + // silent + } + // ErrorFeedback.show("Login failed", e); + return false; + } catch (LoginException e) { + log.error("Cannot login", e); + return false; + } + cmsView.authChange(loginContext); + return true; + } + + protected void logout() { + cmsView.logout(); + cmsView.navigateTo("~"); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) + ((NameCallback) callback).setName(usernameT.getText()); + else if (callback instanceof PasswordCallback) + ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); + else if (callback instanceof HttpRequestCallback) + ((HttpRequestCallback) callback).setRequest(RWT.getRequest()); + else if (callback instanceof LanguageCallback && localeChoice != null) + ((LanguageCallback) callback).setLocale(localeChoice.getSelectedLocale()); + } + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLoginShell.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLoginShell.java new file mode 100644 index 000000000..29a3f54e6 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CmsLoginShell.java @@ -0,0 +1,70 @@ +package org.argeo.cms.widgets.auth; + +import org.argeo.cms.CmsView; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; + +/** The site-related user menu */ +public class CmsLoginShell extends CmsLogin { + private final Shell shell; + + public CmsLoginShell(CmsView cmsView) { + super(cmsView); + shell = createShell(); + shell.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); + createUi(shell); + } + + /** To be overridden. */ + protected Shell createShell() { + Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); + shell.setMaximized(true); + return shell; + } + + /** To be overridden. */ + public void open() { + shell.open(); + } + + @Override + protected boolean login() { + boolean success = false; + try { + success = super.login(); + return success; + } finally { + if (success) + closeShell(); + else { + for (Control child : shell.getChildren()) + child.dispose(); + createUi(shell); + shell.layout(); + // TODO error message + } + } + } + + @Override + protected void logout() { + closeShell(); + super.logout(); + } + + protected void closeShell() { + if (!shell.isDisposed()) { + shell.close(); + shell.dispose(); + } + } + + public Shell getShell() { + return shell; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java new file mode 100644 index 000000000..4cb85b755 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java @@ -0,0 +1,290 @@ +package org.argeo.cms.widgets.auth; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * A composite that can populate itself based on {@link Callback}s. It can be + * used directly as a {@link CallbackHandler} or be used by one by calling the + * {@link #createCallbackHandlers(Callback[])}. + *

+ * Supported standard {@link Callback}s are:
+ *

    + *
  • {@link PasswordCallback}
  • + *
  • {@link NameCallback}
  • + *
  • {@link TextOutputCallback}
  • + *
+ *

+ *

+ * Supported Argeo {@link Callback}s are:
+ *

    + *
  • {@link LocaleChoice}
  • + *
+ *

+ */ +public class CompositeCallbackHandler extends Composite implements + CallbackHandler { + private static final long serialVersionUID = -928223893722723777L; + + private boolean wasUsedAlready = false; + private boolean isSubmitted = false; + private boolean isCanceled = false; + + public CompositeCallbackHandler(Composite parent, int style) { + super(parent, style); + } + + @Override + public synchronized void handle(final Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + // reset + if (wasUsedAlready && !isSubmitted() && !isCanceled()) { + cancel(); + for (Control control : getChildren()) + control.dispose(); + isSubmitted = false; + isCanceled = false; + } + + for (Callback callback : callbacks) + checkCallbackSupported(callback); + // create controls synchronously in the UI thread + getDisplay().syncExec(new Runnable() { + + @Override + public void run() { + createCallbackHandlers(callbacks); + } + }); + + if (!wasUsedAlready) + wasUsedAlready = true; + +// while (!isSubmitted() && !isCanceled()) { +// try { +// wait(1000l); +// } catch (InterruptedException e) { +// // silent +// } +// } + +// cleanCallbacksAfterCancel(callbacks); + } + + public void checkCallbackSupported(Callback callback) + throws UnsupportedCallbackException { + if (callback instanceof TextOutputCallback + || callback instanceof NameCallback + || callback instanceof PasswordCallback + || callback instanceof LocaleChoice) { + return; + } else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Set writable callbacks to null if the handle is canceled (check is done + * by the method) + */ + public void cleanCallbacksAfterCancel(Callback[] callbacks) { + if (isCanceled()) { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(null); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pCallback = (PasswordCallback) callback; + char[] arr = pCallback.getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + pCallback.setPassword(null); + } + } + } + } + } + + public void createCallbackHandlers(Callback[] callbacks) { + Composite composite = this; + for (int i = 0; i < callbacks.length; i++) { + Callback callback = callbacks[i]; + if (callback instanceof TextOutputCallback) { + createLabelTextoutputHandler(composite, + (TextOutputCallback) callback); + } else if (callback instanceof NameCallback) { + createNameHandler(composite, (NameCallback) callback); + } else if (callback instanceof PasswordCallback) { + createPasswordHandler(composite, (PasswordCallback) callback); + } else if (callback instanceof LocaleChoice) { + createLocaleHandler(composite, (LocaleChoice) callback); + } + } + } + + protected Text createNameHandler(Composite composite, + final NameCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD + | SWT.BORDER); + if (callback.getDefaultName() != null) { + // set default value, if provided + text.setText(callback.getDefaultName()); + callback.setName(callback.getDefaultName()); + } + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7300032545287292973L; + + public void modifyText(ModifyEvent event) { + callback.setName(text.getText()); + } + }); + text.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + + text.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8698107785092095713L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + } + }); + return text; + } + + protected Text createPasswordHandler(Composite composite, + final PasswordCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD + | SWT.PASSWORD | SWT.BORDER); + passwordText + .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + passwordText.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -7099363995047686732L; + + public void modifyText(ModifyEvent event) { + callback.setPassword(passwordText.getTextChars()); + } + }); + passwordText.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + return passwordText; + } + + protected Combo createLocaleHandler(Composite composite, + final LocaleChoice callback) { + String[] labels = callback.getSupportedLocalesLabels(); + if (labels.length == 0) + return null; + Label label = new Label(composite, SWT.NONE); + label.setText("Language"); + + final Combo combo = new Combo(composite, SWT.READ_ONLY); + combo.setItems(labels); + combo.select(callback.getDefaultIndex()); + combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + combo.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 38678989091946277L; + + @Override + public void widgetSelected(SelectionEvent e) { + callback.setSelectedIndex(combo.getSelectionIndex()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + return combo; + } + + protected Label createLabelTextoutputHandler(Composite composite, + final TextOutputCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getMessage()); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.horizontalSpan = 2; + label.setLayoutData(data); + return label; + // TODO: find a way to pass this information + // int messageType = callback.getMessageType(); + // int dialogMessageType = IMessageProvider.NONE; + // switch (messageType) { + // case TextOutputCallback.INFORMATION: + // dialogMessageType = IMessageProvider.INFORMATION; + // break; + // case TextOutputCallback.WARNING: + // dialogMessageType = IMessageProvider.WARNING; + // break; + // case TextOutputCallback.ERROR: + // dialogMessageType = IMessageProvider.ERROR; + // break; + // } + // setMessage(callback.getMessage(), dialogMessageType); + } + + synchronized boolean isSubmitted() { + return isSubmitted; + } + + synchronized boolean isCanceled() { + return isCanceled; + } + + protected synchronized void submit() { + isSubmitted = true; + notifyAll(); + } + + protected synchronized void cancel() { + isCanceled = true; + notifyAll(); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java new file mode 100644 index 000000000..b8de34b7c --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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. + */ +package org.argeo.cms.widgets.auth; + +import javax.security.auth.callback.CallbackHandler; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Default authentication dialog, to be used as {@link CallbackHandler}. */ +public class DefaultLoginDialog extends AbstractLoginDialog { + private static final long serialVersionUID = -8551827590693035734L; + + public DefaultLoginDialog() { + this(Display.getCurrent().getActiveShell()); + } + + public DefaultLoginDialog(Shell parentShell) { + super(parentShell); + } + + protected Point getInitialSize() { + return new Point(350, 180); + } + + @Override + protected Control createContents(Composite parent) { + Control control = super.createContents(parent); + parent.pack(); + + // Move the dialog to the center of the top level shell. + Rectangle shellBounds; + if (Display.getCurrent().getActiveShell() != null) // RCP + shellBounds = Display.getCurrent().getActiveShell().getBounds(); + else + shellBounds = Display.getCurrent().getBounds();// RAP + Point dialogSize = parent.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + parent.setLocation(x, y); + return control; + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + CompositeCallbackHandler composite = new CompositeCallbackHandler( + dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + composite.createCallbackHandlers(getCallbacks()); + return composite; + } + + public void internalHandle() { + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/LocaleChoice.java b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/LocaleChoice.java new file mode 100644 index 000000000..009c3721a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/widgets/auth/LocaleChoice.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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. + */ +package org.argeo.cms.widgets.auth; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.callback.LanguageCallback; + +import org.argeo.cms.CmsException; +import org.argeo.cms.i18n.LocaleUtils; + +/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ +public class LocaleChoice { + private final List locales; + + private Integer selectedIndex = null; + private final Integer defaultIndex; + + public LocaleChoice(List locales, Locale defaultLocale) { + Integer defaultIndex = null; + this.locales = Collections.unmodifiableList(locales); + for (int i = 0; i < locales.size(); i++) + if (locales.get(i).equals(defaultLocale)) + defaultIndex = i; + + // based on language only + if (defaultIndex == null) + for (int i = 0; i < locales.size(); i++) + if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage())) + defaultIndex = i; + + if (defaultIndex == null) + throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales); + this.defaultIndex = defaultIndex; + + this.selectedIndex = defaultIndex; + } + + /** + * Convenience constructor based on a comma separated list of iso codes (en, + * en_US, fr_CA, etc.). Default selection is default locale. + */ + public LocaleChoice(String locales, Locale defaultLocale) { + this(LocaleUtils.asLocaleList(locales), defaultLocale); + } + + public String[] getSupportedLocalesLabels() { + String[] labels = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + if (locale.getCountry().equals("")) + labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; + else + labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" + + locale.getLanguage() + "_" + locale.getCountry() + "]"; + + } + return labels; + } + + public Locale getSelectedLocale() { + if (selectedIndex == null) + return null; + return locales.get(selectedIndex); + } + + public void setSelectedIndex(Integer selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public Integer getSelectedIndex() { + return selectedIndex; + } + + public Integer getDefaultIndex() { + return defaultIndex; + } + + public List getLocales() { + return locales; + } + + public Locale getDefaultLocale() { + return locales.get(getDefaultIndex()); + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/security/core/AbstractSystemExecution.java b/org.argeo.cms.ui/src/org/argeo/security/core/AbstractSystemExecution.java new file mode 100644 index 000000000..9c3e5cd83 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/security/core/AbstractSystemExecution.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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. + */ +package org.argeo.security.core; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; + +/** Provides base method for executing code with system authorization. */ +public abstract class AbstractSystemExecution { + private final static Log log = LogFactory.getLog(AbstractSystemExecution.class); + private final Subject subject = new Subject(); + + private final String loginModule = "SYSTEM"; + + /** + * Authenticate the calling thread to the underlying + * {@link AuthenticationManager} + */ + protected void authenticateAsSystem() { + ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { + LoginContext lc = new LoginContext(loginModule, subject); + lc.login(); + } catch (LoginException e) { + throw new CmsException("Cannot login as system", e); + } finally { + Thread.currentThread().setContextClassLoader(origClassLoader); + } + if (log.isTraceEnabled()) + log.trace("System authenticated"); + } + + protected void deauthenticateAsSystem() { + ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { + LoginContext lc = new LoginContext(loginModule, subject); + lc.logout(); + } catch (LoginException e) { + throw new CmsException("Cannot logout as system", e); + } finally { + Thread.currentThread().setContextClassLoader(origClassLoader); + } + } + + protected Subject getSubject() { + return subject; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java b/org.argeo.cms.ui/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java new file mode 100644 index 000000000..aa3827c92 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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. + */ +package org.argeo.security.core; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.Subject; + +import org.eclipse.gemini.blueprint.context.DependencyInitializationAwareBeanPostProcessor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.support.AbstractBeanFactory; +import org.springframework.beans.factory.support.SecurityContextProvider; +import org.springframework.beans.factory.support.SimpleSecurityContextProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Executes with a system authentication the instantiation and initialization + * methods of the application context where it has been defined. + */ +public class AuthenticatedApplicationContextInitialization extends + AbstractSystemExecution implements + DependencyInitializationAwareBeanPostProcessor, ApplicationContextAware { + /** If non empty, restricts to these beans */ + private List beanNames = new ArrayList(); + + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (beanNames.size() == 0 || beanNames.contains(beanName)) + authenticateAsSystem(); + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (beanNames.size() == 0 || beanNames.contains(beanName)) + deauthenticateAsSystem(); + return bean; + } + + public void setBeanNames(List beanNames) { + this.beanNames = beanNames; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + if (applicationContext.getAutowireCapableBeanFactory() instanceof AbstractBeanFactory) { + final AbstractBeanFactory beanFactory = ((AbstractBeanFactory) applicationContext + .getAutowireCapableBeanFactory()); + // retrieve subject's access control context + // and set it as the bean factory security context + Subject.doAs(getSubject(), new PrivilegedAction() { + @Override + public Void run() { + SecurityContextProvider scp = new SimpleSecurityContextProvider( + AccessController.getContext()); + beanFactory.setSecurityContextProvider(scp); + return null; + } + }); + } + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/security/core/OsgiModuleLabel.java b/org.argeo.cms.ui/src/org/argeo/security/core/OsgiModuleLabel.java new file mode 100644 index 000000000..45c9e16b0 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/security/core/OsgiModuleLabel.java @@ -0,0 +1,41 @@ +package org.argeo.security.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; + +/** + * Logs the name and version of an OSGi bundle based on its + * {@link BundleContext}. + */ +public class OsgiModuleLabel { + private final static Log log = LogFactory.getLog(OsgiModuleLabel.class); + + private Bundle bundle; + + public OsgiModuleLabel() { + } + + /** Sets without logging. */ + public OsgiModuleLabel(Bundle bundle) { + this.bundle = bundle; + } + + /** + * Retrieved bundle from a bundle context and logs it. Typically to be set + * as a Spring bean. + */ + public void setBundleContext(BundleContext bundleContext) { + this.bundle = bundleContext.getBundle(); + log.info(msg()); + } + + public String msg() { + String name = bundle.getHeaders().get(Constants.BUNDLE_NAME).toString(); + String symbolicName = bundle.getSymbolicName(); + String version = bundle.getVersion().toString(); + return name + " v" + version + " (" + symbolicName + ")"; + } +} diff --git a/org.argeo.cms.ui/src/org/argeo/security/core/SimpleRoleRegistration.java b/org.argeo.cms.ui/src/org/argeo/security/core/SimpleRoleRegistration.java new file mode 100644 index 000000000..58f6686ac --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/security/core/SimpleRoleRegistration.java @@ -0,0 +1,89 @@ +package org.argeo.security.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +/** + * Register one or many roles via a user admin service. Does nothing if the role + * is already registered. + */ +public class SimpleRoleRegistration implements Runnable { + private final static Log log = LogFactory + .getLog(SimpleRoleRegistration.class); + + private String role; + private List roles = new ArrayList(); + private UserAdmin userAdmin; + private UserTransaction userTransaction; + + @Override + public void run() { + try { + userTransaction.begin(); + if (role != null && !roleExists(role)) + newRole(toDn(role)); + + for (String r : roles) + if (!roleExists(r)) + newRole(toDn(r)); + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Cannot rollback", e1); + } + throw new CmsException("Cannot add roles", e); + } + } + + private boolean roleExists(String role) { + return userAdmin.getRole(toDn(role).toString()) != null; + } + + protected void newRole(LdapName r) { + userAdmin.createRole(r.toString(), Role.GROUP); + log.info("Added role " + r + " required by application."); + } + + public void register(UserAdmin userAdminService, Map properties) { + this.userAdmin = userAdminService; + run(); + } + + protected LdapName toDn(String name) { + try { + return new LdapName("cn=" + name + ",ou=roles,ou=node"); + } catch (InvalidNameException e) { + throw new CmsException("Badly formatted role name " + name, e); + } + } + + public void setRole(String role) { + this.role = role; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public void setUserAdmin(UserAdmin userAdminService) { + this.userAdmin = userAdminService; + } + + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +} diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 8b2080898..b0994c6a2 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -1,27 +1,10 @@ Bundle-SymbolicName: org.argeo.cms;singleton:=true Bundle-Activator: org.argeo.cms.internal.kernel.Activator Import-Package: javax.jcr.security,\ -org.xml.sax,\ -org.argeo.jcr,\ -org.argeo.jcr.docbook,\ -org.apache.jackrabbit.api,\ -org.apache.jackrabbit.commons,\ -org.apache.jackrabbit.core.security.user,\ -org.argeo.eclipse.ui.dialogs;resolution:=optional,\ -org.eclipse.*;resolution:=optional,\ -org.eclipse.core.commands;resolution:=optional,\ -org.eclipse.swt;resolution:=optional,\ -org.eclipse.jface.window;resolution:=optional,\ org.h2;resolution:=optional,\ org.postgresql;resolution:=optional,\ -org.apache.commons.vfs2.*;resolution:=optional,\ -org.apache.jackrabbit.*;resolution:=optional,\ -org.joda.time.*;resolution:=optional,\ -org.springframework.context,\ -org.springframework.core.io,\ org.apache.jackrabbit.webdav.server,\ org.apache.jackrabbit.webdav.jcr,\ -org.bouncycastle.asn1.x509,\ org.eclipse.equinox.http.jetty,\ * Provide-Capability: cms.datamodel;name=cms;cnd=/org/argeo/cms/cms.cnd \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/CmsView.java b/org.argeo.cms/src/org/argeo/cms/CmsView.java index 8b56c137b..75cefd1c0 100644 --- a/org.argeo.cms/src/org/argeo/cms/CmsView.java +++ b/org.argeo.cms/src/org/argeo/cms/CmsView.java @@ -3,8 +3,6 @@ package org.argeo.cms; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; -import org.argeo.cms.ui.UxContext; - /** Provides interaction with the CMS system. UNSTABLE API at this stage. */ public interface CmsView { public final static String KEY = "org.argeo.cms.view"; diff --git a/org.argeo.cms/src/org/argeo/cms/DataMigration.java b/org.argeo.cms/src/org/argeo/cms/DataMigration.java new file mode 100644 index 000000000..f75e66bd0 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/DataMigration.java @@ -0,0 +1,18 @@ +package org.argeo.cms; + +import java.util.Map; + +import javax.jcr.Session; + +public interface DataMigration { + /** Migrate data between two workspaces, at JCR level. */ + Boolean migrate(Session source, Session target); + + /** + * Keys are the source workspaces and values the target workspaces. If null + * is returned, only the default workspace will be migrated, to the default + * workspace of the target repository. + */ + Map workspacesToMigrate(); + +} diff --git a/org.argeo.cms/src/org/argeo/cms/UxContext.java b/org.argeo.cms/src/org/argeo/cms/UxContext.java new file mode 100644 index 000000000..b6674b99b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/UxContext.java @@ -0,0 +1,9 @@ +package org.argeo.cms; + +public interface UxContext { + boolean isPortrait(); + boolean isLandscape(); + boolean isSquare(); + + boolean isSmall(); +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java index 8aa08728b..1ec52f057 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java @@ -26,7 +26,7 @@ import javax.security.auth.x500.X500Principal; import org.argeo.cms.CmsException; import org.argeo.cms.CmsView; -import org.argeo.cms.util.CmsUtils; +import org.argeo.eclipse.ui.specific.UiContext; import org.osgi.service.useradmin.Authorization; /** Static utilities */ @@ -49,12 +49,11 @@ public final class CurrentUser { public static boolean isAnonymous(Subject subject) { String username = getUsername(subject); - return username == null - || username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS); + return username == null || username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS); } private static Subject currentSubject() { - CmsView cmsView = CmsUtils.getCmsView(); + CmsView cmsView = getCmsView(); if (cmsView != null) return cmsView.getSubject(); Subject subject = Subject.getSubject(AccessController.getContext()); @@ -63,14 +62,21 @@ public final class CurrentUser { throw new CmsException("Cannot find related subject"); } + /** + * The CMS view related to this display, or null if none is available from + * this call. + */ + private static CmsView getCmsView() { + return UiContext.getData(CmsView.KEY); + } + public final static String getUsername(Subject subject) { // Subject subject = Subject.getSubject(AccessController.getContext()); // if (subject == null) // return null; if (subject.getPrincipals(X500Principal.class).size() != 1) return null; - Principal principal = subject.getPrincipals(X500Principal.class) - .iterator().next(); + Principal principal = subject.getPrincipals(X500Principal.class).iterator().next(); return principal.getName(); } @@ -80,8 +86,7 @@ public final class CurrentUser { } private static Authorization getAuthorization(Subject subject) { - return subject.getPrivateCredentials(Authorization.class).iterator() - .next(); + return subject.getPrivateCredentials(Authorization.class).iterator().next(); } public final static Set roles() { @@ -90,8 +95,7 @@ public final class CurrentUser { public final static Set roles(Subject subject) { Set roles = new HashSet(); - X500Principal userPrincipal = subject - .getPrincipals(X500Principal.class).iterator().next(); + X500Principal userPrincipal = subject.getPrincipals(X500Principal.class).iterator().next(); roles.add(userPrincipal.getName()); for (Principal group : subject.getPrincipals(Group.class)) { roles.add(group.getName()); diff --git a/org.argeo.cms/src/org/argeo/cms/forms/EditableLink.java b/org.argeo.cms/src/org/argeo/cms/forms/EditableLink.java deleted file mode 100644 index ece0be36b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/EditableLink.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.argeo.cms.forms; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.viewers.EditablePart; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String that displays a browsable link when read-only */ -public class EditableLink extends EditablePropertyString implements - EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String type; - private String message; - private boolean readOnly; - - public EditableLink(Composite parent, int style, Node node, - String propertyName, String type, String message) - throws RepositoryException { - super(parent, style, node, propertyName, message); - this.message = message; - this.type = type; - - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(node.getProperty(propertyName).getString()); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(""); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else if (readOnly) - setLinkValue(lbl, text); - else - // if canEdit() we put only the value with no link - // to avoid glitches of the edition life cycle - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - txt.setMessage(message); - } else - txt.setText(text); - } - } - - private void setLinkValue(Label lbl, String text) { - if (FormStyle.email.style().equals(type)) - lbl.setText(FormUtils.getMailLink(text)); - else if (FormStyle.phone.style().equals(type)) - lbl.setText(FormUtils.getPhoneLink(text)); - else if (FormStyle.website.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - else if (FormStyle.facebook.style().equals(type) - || FormStyle.instagram.style().equals(type) - || FormStyle.linkedIn.style().equals(type) - || FormStyle.twitter.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/EditableMultiStringProperty.java b/org.argeo.cms/src/org/argeo/cms/forms/EditableMultiStringProperty.java deleted file mode 100644 index fdaa0365c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/EditableMultiStringProperty.java +++ /dev/null @@ -1,267 +0,0 @@ -package org.argeo.cms.forms; - -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Display, add or remove values from a list in a CMS context */ -public class EditableMultiStringProperty extends StyledControl implements - EditablePart { - private static final long serialVersionUID = -7044614381252178595L; - - private String propertyName; - private String message; - // TODO implement the ability to provide a list of legal values - private String[] possibleValues; - private boolean canEdit; - private SelectionListener removeValueSL; - private List values; - - // TODO manage within the CSS - private int rowSpacing = 5; - private int rowMarging = 0; - private int oneValueMargingRight = 5; - private int btnWidth = 16; - private int btnHeight = 16; - private int btnHorizontalIndent = 3; - - public EditableMultiStringProperty(Composite parent, int style, Node node, - String propertyName, List values, String[] possibleValues, - String addValueMsg, SelectionListener removeValueSelectionListener) - throws RepositoryException { - super(parent, style, node, true); - - this.propertyName = propertyName; - this.values = values; - this.possibleValues = possibleValues; - this.message = addValueMsg; - this.canEdit = removeValueSelectionListener != null; - this.removeValueSL = removeValueSelectionListener; - } - - public List getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - - // Row layout items do not need explicit layout data - protected void setControlLayoutData(Control control) { - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsUtils.fillWidth()); - } - - @Override - public Control getControl() { - return super.getControl(); - } - - @Override - protected Control createControl(Composite box, String style) { - Composite row = new Composite(box, SWT.NO_FOCUS); - row.setLayoutData(EclipseUiUtils.fillAll()); - - RowLayout rl = new RowLayout(SWT.HORIZONTAL); - rl.wrap = true; - rl.spacing = rowSpacing; - rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging; - row.setLayout(rl); - - if (values != null) { - for (final String value : values) { - if (canEdit) - createRemovableValue(row, SWT.SINGLE, value); - else - createValueLabel(row, SWT.SINGLE, value); - } - } - - if (!canEdit) - return row; - else if (isEditing()) - return createText(row, style); - else - return createLabel(row, style); - } - - /** - * Override to provide specific layout for the existing values, typically - * adding a pound (#) char for tags or anchor info for browsable links. We - * assume the parent composite already has a layout and it is the caller - * responsibility to apply corresponding layout data - */ - protected Label createValueLabel(Composite parent, int style, String value) { - Label label = new Label(parent, style); - label.setText("#" + value); - CmsUtils.markup(label); - CmsUtils.style(label, FormStyle.propertyText.style()); - return label; - } - - private Composite createRemovableValue(Composite parent, int style, - String value) { - Composite valCmp = new Composite(parent, SWT.NO_FOCUS); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, - false)); - gl.marginRight = oneValueMargingRight; - valCmp.setLayout(gl); - - createValueLabel(valCmp, SWT.WRAP, value); - - Button deleteBtn = new Button(valCmp, SWT.FLAT); - deleteBtn.setData(FormConstants.LINKED_VALUE, value); - deleteBtn.addSelectionListener(removeValueSL); - CmsUtils.style(deleteBtn, FormStyle.delete.style() - + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(); - gd.heightHint = btnHeight; - gd.widthHint = btnWidth; - gd.horizontalIndent = btnHorizontalIndent; - deleteBtn.setLayoutData(gd); - - return valCmp; - } - - protected Text createText(Composite box, String style) { - final Text text = new Text(box, getStyle()); - // The "add new value" text is not meant to change, so we can set it on - // creation - text.setMessage(message); - CmsUtils.style(text, style); - text.setFocus(); - - text.addTraverseListener(new TraverseListener() { - private static final long serialVersionUID = 1L; - - public void keyTraversed(TraverseEvent e) { - if (e.keyCode == SWT.CR) { - addValue(text); - e.doit = false; - } - } - }); - - // The OK button does not work with the focusOut listener - // because focus out is called before the OK button is pressed - - // // we must call layout() now so that the row data can compute the - // height - // // of the other controls. - // text.getParent().layout(); - // int height = text.getSize().y; - // - // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM); - // okBtn.setText("OK"); - // RowData rd = new RowData(SWT.DEFAULT, height - 2); - // okBtn.setLayoutData(rd); - // - // okBtn.addSelectionListener(new SelectionAdapter() { - // private static final long serialVersionUID = 2780819012423622369L; - // - // @Override - // public void widgetSelected(SelectionEvent e) { - // addValue(text); - // } - // }); - - return text; - } - - /** Performs the real addition, overwrite to make further sanity checks */ - protected void addValue(Text text) { - String value = text.getText(); - String errMsg = null; - - if (EclipseUiUtils.isEmpty(value)) - return; - - if (values.contains(value)) - errMsg = "Dupplicated value: " + value - + ", please correct and try again"; - if (errMsg != null) - MessageDialog.openError(this.getShell(), "Addition not allowed", - errMsg); - else { - values.add(value); - Composite newCmp = createRemovableValue(text.getParent(), - SWT.SINGLE, value); - newCmp.moveAbove(text); - text.setText(""); - newCmp.getParent().layout(); - } - } - - protected Label createLabel(Composite box, String style) { - if (canEdit) { - Label lbl = new Label(box, getStyle()); - lbl.setText(message); - CmsUtils.style(lbl, style); - CmsUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - return null; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (canEdit) - lbl.setText(text); - else - lbl.setText(""); - } else if (child instanceof Text) { - Text txt = (Text) child; - txt.setText(text); - } - } - - public synchronized void startEditing() { - getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - getControl().setData(STYLE, FormStyle.propertyMessage.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyDate.java b/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyDate.java deleted file mode 100644 index 928a28508..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyDate.java +++ /dev/null @@ -1,304 +0,0 @@ -package org.argeo.cms.forms; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.DateTime; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** CMS form part to display and edit a date */ -public class EditablePropertyDate extends StyledControl implements EditablePart { - private static final long serialVersionUID = 2500215515778162468L; - - // Context - private String propertyName; - private String message; - private DateFormat dateFormat; - - // UI Objects - private Text dateTxt; - private Button openCalBtn; - - // TODO manage within the CSS - private int fieldBtnSpacing = 5; - - /** - * - * @param parent - * @param style - * @param node - * @param propertyName - * @param message - * @param dateFormat - * provide a {@link DateFormat} as contract to be able to - * read/write dates as strings - * @throws RepositoryException - */ - public EditablePropertyDate(Composite parent, int style, Node node, - String propertyName, String message, DateFormat dateFormat) - throws RepositoryException { - super(parent, style, node, false); - - this.propertyName = propertyName; - this.message = message; - this.dateFormat = dateFormat; - - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(dateFormat.format(node.getProperty(propertyName) - .getDate().getTime())); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(message); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - } else - txt.setText(text); - } - } - - public synchronized void startEditing() { - // if (dateTxt != null && !dateTxt.isDisposed()) - getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(dateTxt.getText())) - getControl().setData(STYLE, FormStyle.propertyMessage.style()); - else - getControl().setData(STYLE, FormStyle.propertyText.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createCustomEditableControl(box, style); - } else - return createLabel(box, style); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsUtils.fillWidth()); - CmsUtils.style(lbl, style); - CmsUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - private Control createCustomEditableControl(Composite box, String style) { - box.setLayoutData(CmsUtils.fillWidth()); - Composite dateComposite = new Composite(box, SWT.NONE); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, - false)); - gl.horizontalSpacing = fieldBtnSpacing; - dateComposite.setLayout(gl); - dateTxt = new Text(dateComposite, SWT.BORDER); - CmsUtils.style(dateTxt, style); - dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT)); - dateTxt.setToolTipText("Enter a date with form \"" - + FormUtils.DEFAULT_SHORT_DATE_FORMAT - + "\" or use the calendar"); - openCalBtn = new Button(dateComposite, SWT.FLAT); - CmsUtils.style(openCalBtn, FormStyle.calendar.style() - + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); - gd.heightHint = 17; - openCalBtn.setLayoutData(gd); - // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN); - - openCalBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - public void widgetSelected(SelectionEvent event) { - CalendarPopup popup = new CalendarPopup(dateTxt); - popup.open(); - } - }); - - // dateTxt.addFocusListener(new FocusListener() { - // private static final long serialVersionUID = 1L; - // - // @Override - // public void focusLost(FocusEvent event) { - // String newVal = dateTxt.getText(); - // // Enable reset of the field - // if (FormUtils.notNull(newVal)) - // calendar = null; - // else { - // try { - // Calendar newCal = parseDate(newVal); - // // DateText.this.setText(newCal); - // calendar = newCal; - // } catch (ParseException pe) { - // // Silent. Manage error popup? - // if (calendar != null) - // EditablePropertyDate.this.setText(calendar); - // } - // } - // } - // - // @Override - // public void focusGained(FocusEvent event) { - // } - // }); - return dateTxt; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep || child instanceof Label) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - /** Enable setting a custom tooltip on the underlying text */ - @Deprecated - public void setToolTipText(String toolTipText) { - dateTxt.setToolTipText(toolTipText); - } - - @Deprecated - /** Enable setting a custom message on the underlying text */ - public void setMessage(String message) { - dateTxt.setMessage(message); - } - - @Deprecated - public void setText(Calendar cal) { - String newValueStr = ""; - if (cal != null) - newValueStr = dateFormat.format(cal.getTime()); - if (!newValueStr.equals(dateTxt.getText())) - dateTxt.setText(newValueStr); - } - - // UTILITIES TO MANAGE THE CALENDAR POPUP - // TODO manage the popup shell in a cleaner way - private class CalendarPopup extends Shell { - private static final long serialVersionUID = 1L; - private DateTime dateTimeCtl; - - public CalendarPopup(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - populate(); - // Add border and shadow style - CmsUtils.markup(CalendarPopup.this); - CmsUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style()); - pack(); - layout(); - setLocation(source.toDisplay((source.getLocation().x - 2), - (source.getSize().y) + 3)); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - open(); - } - - private void setProperty() { - // Direct set does not seems to work. investigate - // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(), - // dateTimeCtl.getDay(), 12, 0); - Calendar cal = new GregorianCalendar(); - cal.set(Calendar.YEAR, dateTimeCtl.getYear()); - cal.set(Calendar.MONTH, dateTimeCtl.getMonth()); - cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay()); - String dateStr = dateFormat.format(cal.getTime()); - dateTxt.setText(dateStr); - } - - protected void populate() { - setLayout(EclipseUiUtils.noSpaceGridLayout()); - - dateTimeCtl = new DateTime(this, SWT.CALENDAR); - dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll()); - - Calendar calendar = FormUtils.parseDate(dateFormat, - dateTxt.getText()); - - if (calendar != null) - dateTimeCtl.setDate(calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH)); - - dateTimeCtl.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -8414377364434281112L; - - @Override - public void widgetSelected(SelectionEvent e) { - setProperty(); - } - }); - - dateTimeCtl.addMouseListener(new MouseListener() { - private static final long serialVersionUID = 1L; - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - setProperty(); - close(); - dispose(); - } - }); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyString.java b/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyString.java deleted file mode 100644 index dd3ff29dc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/EditablePropertyString.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.cms.forms; - -import static org.argeo.cms.forms.FormStyle.propertyMessage; -import static org.argeo.cms.forms.FormStyle.propertyText; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.widgets.EditableText; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String in a CMS context */ -public class EditablePropertyString extends EditableText implements - EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String propertyName; - private String message; - - // encode the '&' character in rap - private final static String AMPERSAND = "&"; - private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)"; - - public EditablePropertyString(Composite parent, int style, Node node, - String propertyName, String message) throws RepositoryException { - super(parent, style, node, true); - - this.propertyName = propertyName; - this.message = message; - - if (node.hasProperty(propertyName)) { - this.setStyle(propertyText.style()); - this.setText(node.getProperty(propertyName).getString()); - } else { - this.setStyle(propertyMessage.style()); - this.setText(message + " "); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message + " "); - else - // TODO enhance this - lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND)); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - txt.setMessage(message + " "); - } else - txt.setText(text.replaceAll("
", "\n")); - } - } - - public synchronized void startEditing() { - getControl().setData(STYLE, propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(((Text) getControl()).getText())) - getControl().setData(STYLE, propertyMessage.style()); - else - getControl().setData(STYLE, propertyText.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/FormConstants.java b/org.argeo.cms/src/org/argeo/cms/forms/FormConstants.java deleted file mode 100644 index 18df3e47f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/FormConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.cms.forms; - -/** Constants used in the various CMS Forms */ -public interface FormConstants { - // DATAKEYS - public final static String LINKED_VALUE = "LinkedValue"; -} diff --git a/org.argeo.cms/src/org/argeo/cms/forms/FormEditorHeader.java b/org.argeo.cms/src/org/argeo/cms/forms/FormEditorHeader.java deleted file mode 100644 index c12e2f000..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/FormEditorHeader.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.argeo.cms.forms; - -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; - -/** Add life cycle management abilities to an editable form page */ -public class FormEditorHeader implements SelectionListener, Observer { - private static final long serialVersionUID = 7392898696542484282L; - - // private final Node context; - private final CmsEditable cmsEditable; - private Button publishBtn; - - // Should we provide here the ability to switch from read only to edition - // mode? - // private Button editBtn; - // private boolean readOnly; - - // TODO add information about the current node status, typically if it is - // dirty or not - - private Composite parent; - private Composite display; - private Object layoutData; - - public FormEditorHeader(Composite parent, int style, Node context, - CmsEditable cmsEditable) { - this.cmsEditable = cmsEditable; - this.parent = parent; - // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - // this.context = context; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - refresh(); - } - - public void setLayoutData(Object layoutData) { - this.layoutData = layoutData; - if (display != null && !display.isDisposed()) - display.setLayoutData(layoutData); - } - - protected void refresh() { - if (display != null && !display.isDisposed()) - display.dispose(); - - display = new Composite(parent, SWT.NONE); - display.setLayoutData(layoutData); - - CmsUtils.style(display, FormStyle.header.style()); - display.setBackgroundMode(SWT.INHERIT_FORCE); - - display.setLayout(CmsUtils.noSpaceGridLayout()); - - publishBtn = createSimpleBtn(display, getPublishButtonLabel()); - display.moveAbove(null); - parent.layout(); - } - - private Button createSimpleBtn(Composite parent, String label) { - Button button = new Button(parent, SWT.FLAT | SWT.PUSH); - button.setText(label); - CmsUtils.style(button, FormStyle.header.style()); - button.addSelectionListener(this); - return button; - } - - private String getPublishButtonLabel() { - // Rather check if the current node differs from what has been - // previously committed - // For the time being, we always reach here, the underlying CmsEditable - // is always editing. - if (cmsEditable.isEditing()) - return " Publish "; - else - return " Edit "; - } - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.getSource() == publishBtn) { - // For the time being, the underlying CmsEditable - // is always editing when we reach this point - if (cmsEditable.isEditing()) { - // we always leave the node in a check outed state - cmsEditable.stopEditing(); - cmsEditable.startEditing(); - } else { - cmsEditable.startEditing(); - } - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) { - refresh(); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/FormPageViewer.java b/org.argeo.cms/src/org/argeo/cms/forms/FormPageViewer.java deleted file mode 100644 index b4d24bf95..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/FormPageViewer.java +++ /dev/null @@ -1,641 +0,0 @@ -package org.argeo.cms.forms; - -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.ValueFormatException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.CmsNames; -import org.argeo.cms.internal.text.MarkupValidatorCopy; -import org.argeo.cms.text.Img; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.AbstractPageViewer; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.viewers.Section; -import org.argeo.cms.viewers.SectionPart; -import org.argeo.cms.widgets.EditableImage; -import org.argeo.cms.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadEvent; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.FormLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Manage life cycle of a form page that is linked to a given node */ -public class FormPageViewer extends AbstractPageViewer { - private final static Log log = LogFactory.getLog(FormPageViewer.class); - private static final long serialVersionUID = 5277789504209413500L; - - private final Section mainSection; - - // TODO manage within the CSS - private int labelColWidth = 150; - private int rowLayoutHSpacing = 8; - - // Context cached in the viewer - // The reference to translate from text to calendar and reverse - private DateFormat dateFormat = new SimpleDateFormat( - FormUtils.DEFAULT_SHORT_DATE_FORMAT); - private CmsImageManager imageManager; - private FileUploadListener fileUploadListener; - - public FormPageViewer(Section mainSection, int style, - CmsEditable cmsEditable) throws RepositoryException { - super(mainSection, style, cmsEditable); - this.mainSection = mainSection; - - if (getCmsEditable().canEdit()) { - fileUploadListener = new FUL(); - } - } - - @Override - protected void prepare(EditablePart part, Object caretPosition) { - if (part instanceof Img) { - ((Img) part).setFileUploadListener(fileUploadListener); - } - } - - /** To be overridden.Save the edited part. */ - protected void save(EditablePart part) throws RepositoryException { - Node node = null; - if (part instanceof EditableMultiStringProperty) { - EditableMultiStringProperty ept = (EditableMultiStringProperty) part; - // SWT : View - List values = ept.getValues(); - // JCR : Model - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (values.isEmpty()) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - node.setProperty(propName, values.toArray(new String[0])); - } - // => Viewer : Controller - } else if (part instanceof EditablePropertyString) { - EditablePropertyString ept = (EditablePropertyString) part; - // SWT : View - String txt = ((Text) ept.getControl()).getText(); - // JCR : Model - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (EclipseUiUtils.isEmpty(txt)) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - setPropertySilently(node, propName, txt); - // node.setProperty(propName, txt); - } - // node.getSession().save(); - // => Viewer : Controller - } else if (part instanceof EditablePropertyDate) { - EditablePropertyDate ept = (EditablePropertyDate) part; - Calendar cal = FormUtils.parseDate(dateFormat, - ((Text) ept.getControl()).getText()); - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (cal == null) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - node.setProperty(propName, cal); - } - // node.getSession().save(); - // => Viewer : Controller - } - // TODO: make this configurable, sometimes we do not want to save the - // current session at this stage - if (node != null && node.getSession().hasPendingChanges()) { - JcrUtils.updateLastModified(node); - node.getSession().save(); - } - } - - @Override - protected void updateContent(EditablePart part) throws RepositoryException { - if (part instanceof EditableMultiStringProperty) { - EditableMultiStringProperty ept = (EditableMultiStringProperty) part; - // SWT : View - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - List valStrings = new ArrayList(); - if (node.hasProperty(propName)) { - Value[] values = node.getProperty(propName).getValues(); - for (Value val : values) - valStrings.add(val.getString()); - } - ept.setValues(valStrings); - } else if (part instanceof EditablePropertyString) { - // || part instanceof EditableLink - EditablePropertyString ept = (EditablePropertyString) part; - // JCR : Model - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - if (node.hasProperty(propName)) { - String value = node.getProperty(propName).getString(); - ept.setText(value); - } else - ept.setText(""); - // => Viewer : Controller - } else if (part instanceof EditablePropertyDate) { - EditablePropertyDate ept = (EditablePropertyDate) part; - // JCR : Model - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - if (node.hasProperty(propName)) - ept.setText(dateFormat.format(node.getProperty(propName) - .getDate().getTime())); - else - ept.setText(""); - } else if (part instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) part; - Node partNode = sectionPart.getNode(); - // use control AFTER setting style, since it may have been reset - if (part instanceof EditableImage) { - EditableImage editableImage = (EditableImage) part; - imageManager().load(partNode, part.getControl(), - editableImage.getPreferredImageSize()); - } - } - } - - // FILE UPLOAD LISTENER - protected class FUL implements FileUploadListener { - - public FUL() { - } - - public void uploadProgress(FileUploadEvent event) { - // TODO Monitor upload progress - } - - public void uploadFailed(FileUploadEvent event) { - throw new CmsException("Upload failed " + event, - event.getException()); - } - - public void uploadFinished(FileUploadEvent event) { - for (FileDetails file : event.getFileDetails()) { - if (log.isDebugEnabled()) - log.debug("Received: " + file.getFileName()); - } - mainSection.getDisplay().syncExec(new Runnable() { - @Override - public void run() { - saveEdit(); - } - }); - FileUploadHandler uploadHandler = (FileUploadHandler) event - .getSource(); - uploadHandler.dispose(); - } - } - - // FOCUS OUT LISTENER - protected FocusListener createFocusListener() { - return new FocusOutListener(); - } - - private class FocusOutListener implements FocusListener { - private static final long serialVersionUID = -6069205786732354186L; - - @Override - public void focusLost(FocusEvent event) { - saveEdit(); - } - - @Override - public void focusGained(FocusEvent event) { - // does nothing; - } - } - - // MOUSE LISTENER - @Override - protected MouseListener createMouseListener() { - return new ML(); - } - - private class ML extends MouseAdapter { - private static final long serialVersionUID = 8526890859876770905L; - - @Override - public void mouseDoubleClick(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (getCmsEditable().canEdit()) { - if (getCmsEditable().isEditing() - && !(getEdited() instanceof Img)) { - if (source == mainSection) - return; - EditablePart part = findDataParent(source); - upload(part); - } else { - getCmsEditable().startEditing(); - } - } - } - } - - @Override - public void mouseDown(MouseEvent e) { - if (getCmsEditable().isEditing()) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - EditablePart composite = findDataParent(source); - Point point = new Point(e.x, e.y); - if (!(composite instanceof Img)) - edit(composite, source.toDisplay(point)); - } else if (e.button == 3) { - // EditablePart composite = findDataParent((Control) e - // .getSource()); - // if (styledTools != null) - // styledTools.show(composite, new Point(e.x, e.y)); - } - } - } - - protected synchronized void upload(EditablePart part) { - if (part instanceof SectionPart) { - if (part instanceof Img) { - if (getEdited() == part) - return; - edit(part, null); - layout(part.getControl()); - } - } - } - } - - @Override - public Control getControl() { - return mainSection; - } - - protected CmsImageManager imageManager() { - if (imageManager == null) - imageManager = CmsUtils.getCmsView().getImageManager(); - return imageManager; - } - - // LOCAL UI HELPERS - protected Section createSectionIfNeeded(Composite body, Node node) - throws RepositoryException { - Section section = null; - if (node != null) { - section = new Section(body, SWT.NO_FOCUS, node); - section.setLayoutData(CmsUtils.fillWidth()); - section.setLayout(CmsUtils.noSpaceGridLayout()); - } - return section; - } - - protected void createSimpleLT(Composite bodyRow, Node node, - String propName, String label, String msg) - throws RepositoryException { - if (getCmsEditable().canEdit() || node.hasProperty(propName)) { - createPropertyLbl(bodyRow, label); - EditablePropertyString eps = new EditablePropertyString(bodyRow, - SWT.WRAP | SWT.LEFT, node, propName, msg); - eps.setMouseListener(getMouseListener()); - eps.setFocusListener(getFocusListener()); - eps.setLayoutData(CmsUtils.fillWidth()); - } - } - - protected void createMultiStringLT(Composite bodyRow, Node node, - String propName, String label, String msg) - throws RepositoryException { - boolean canEdit = getCmsEditable().canEdit(); - if (canEdit || node.hasProperty(propName)) { - createPropertyLbl(bodyRow, label); - - List valueStrings = new ArrayList(); - - if (node.hasProperty(propName)) { - Value[] values = node.getProperty(propName).getValues(); - for (Value value : values) - valueStrings.add(value.getString()); - } - - // TODO use a drop down to display possible values to the end user - EditableMultiStringProperty emsp = new EditableMultiStringProperty( - bodyRow, SWT.SINGLE | SWT.LEAD, node, propName, - valueStrings, new String[] { "Implement this" }, msg, - canEdit ? getRemoveValueSelListener() : null); - addListeners(emsp); - // emsp.setMouseListener(getMouseListener()); - emsp.setStyle(FormStyle.propertyMessage.style()); - emsp.setLayoutData(CmsUtils.fillWidth()); - } - } - - protected Label createPropertyLbl(Composite parent, String value) { - return createPropertyLbl(parent, value, SWT.TOP); - } - - protected Label createPropertyLbl(Composite parent, String value, int vAlign) { - boolean isSmall = CmsUtils.getCmsView().getUxContext().isSmall(); - Label label = new Label(parent, isSmall ? SWT.LEFT : SWT.RIGHT - | SWT.WRAP); - label.setText(value + " "); - CmsUtils.style(label, FormStyle.propertyLabel.style()); - GridData gd = new GridData(isSmall ? SWT.LEFT : SWT.RIGHT, vAlign, - false, false); - gd.widthHint = labelColWidth; - label.setLayoutData(gd); - return label; - } - - protected Label newStyledLabel(Composite parent, String style, String value) { - Label label = new Label(parent, SWT.NONE); - label.setText(value); - CmsUtils.style(label, style); - return label; - } - - protected Composite createRowLayoutComposite(Composite parent) - throws RepositoryException { - Composite bodyRow = new Composite(parent, SWT.NO_FOCUS); - bodyRow.setLayoutData(CmsUtils.fillWidth()); - RowLayout rl = new RowLayout(SWT.WRAP); - rl.type = SWT.HORIZONTAL; - rl.spacing = rowLayoutHSpacing; - rl.marginHeight = rl.marginWidth = 0; - rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0; - bodyRow.setLayout(rl); - return bodyRow; - } - - protected Composite createAddImgComposite(final Section section, - Composite parent, final Node parentNode) throws RepositoryException { - - Composite body = new Composite(parent, SWT.NO_FOCUS); - body.setLayout(new GridLayout()); - - FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, - parentNode, null); - final FileUploadHandler currentUploadHandler = new FileUploadHandler( - receiver); - if (fileUploadListener != null) - currentUploadHandler.addUploadListener(fileUploadListener); - - // Button creation - final FileUpload fileUpload = new FileUpload(body, SWT.BORDER); - fileUpload.setText("Import an image"); - fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, - true)); - fileUpload.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 4869523412991968759L; - - @Override - public void widgetSelected(SelectionEvent e) { - ServerPushSession pushSession = new ServerPushSession(); - pushSession.start(); - String uploadURL = currentUploadHandler.getUploadUrl(); - fileUpload.submit(uploadURL); - } - }); - - return body; - } - - protected class FormFileUploadReceiver extends FileUploadReceiver implements - CmsNames { - - private Node context; - private Section section; - private String name; - - public FormFileUploadReceiver(Section section, Node context, String name) { - this.context = context; - this.section = section; - this.name = name; - } - - @Override - public void receive(InputStream stream, FileDetails details) - throws IOException { - - if (name == null) - name = details.getFileName(); - - // TODO clean image name more carefully - String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "_"); - - try { - imageManager().uploadImage(context, cleanedName, stream); - // TODO clean refresh strategy - section.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - try { - FormPageViewer.this.refresh(section); - section.layout(); - section.getParent().layout(); - } catch (RepositoryException re) { - throw new CmsException("unable to refresh " - + "image section for " + context); - } - } - }); - } catch (RepositoryException re) { - throw new CmsException("unable to upload image " + name - + " at " + context); - } - } - } - - protected void addListeners(StyledControl control) { - control.setMouseListener(getMouseListener()); - control.setFocusListener(getFocusListener()); - } - - protected Img createImgComposite(Composite parent, Node node, - Point preferredSize) throws RepositoryException { - Img img = new Img(parent, SWT.NONE, node, preferredSize) { - private static final long serialVersionUID = 1297900641952417540L; - - @Override - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, - SWT.DEFAULT)); - } - - @Override - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, - SWT.DEFAULT)); - } - }; - img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); - updateContent(img); - addListeners(img); - return img; - } - - protected Composite addDeleteAbility(final Section section, - final Node sessionNode, int topWeight, int rightWeight) { - Composite comp = new Composite(section, SWT.NONE); - comp.setLayoutData(CmsUtils.fillAll()); - comp.setLayout(new FormLayout()); - - // The body to be populated - Composite body = new Composite(comp, SWT.NO_FOCUS); - body.setLayoutData(EclipseUiUtils.fillFormData()); - - if (getCmsEditable().canEdit()) { - // the delete button - Button deleteBtn = new Button(comp, SWT.FLAT); - CmsUtils.style(deleteBtn, FormStyle.deleteOverlay.style()); - FormData formData = new FormData(); - formData.right = new FormAttachment(rightWeight, 0); - formData.top = new FormAttachment(topWeight, 0); - deleteBtn.setLayoutData(formData); - deleteBtn.moveAbove(body); - - deleteBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 4304223543657238462L; - - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - if (MessageDialog.openConfirm(section.getShell(), - "Confirm deletion", - "Are you really you want to remove this?")) { - Session session; - try { - session = sessionNode.getSession(); - Section parSection = section.getParentSection(); - sessionNode.remove(); - session.save(); - refresh(parSection); - layout(parSection); - } catch (RepositoryException re) { - throw new CmsException("Unable to delete " - + sessionNode, re); - } - - } - - } - }); - } - return body; - } - - // LOCAL HELPERS FOR NODE MANAGEMENT - protected Node getOrCreateNode(Node parent, String nodeType, String nodeName) - throws RepositoryException { - Node node = null; - if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) { - node = JcrUtils.mkdirs(parent, nodeName, nodeType); - parent.getSession().save(); - } - - if (getCmsEditable().canEdit() || parent.hasNode(nodeName)) - node = parent.getNode(nodeName); - - return node; - } - - private SelectionListener getRemoveValueSelListener() { - return new SelectionAdapter() { - private static final long serialVersionUID = 9022259089907445195L; - - @Override - public void widgetSelected(SelectionEvent e) { - Object source = e.getSource(); - if (source instanceof Button) { - Button btn = (Button) source; - Object obj = btn.getData(FormConstants.LINKED_VALUE); - EditablePart ep = findDataParent(btn); - if (ep != null && ep instanceof EditableMultiStringProperty) { - EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep; - List values = emsp.getValues(); - if (values.contains(obj)) { - values.remove(values.indexOf(obj)); - emsp.setValues(values); - try { - save(emsp); - // TODO workaround to force refresh - edit(emsp, 0); - cancelEdit(); - } catch (RepositoryException e1) { - throw new CmsException( - "Unable to remove value " + obj, e1); - } - layout(emsp); - } - } - } - } - }; - } - - protected void setPropertySilently(Node node, String propName, String value) - throws RepositoryException { - try { - // TODO Clean this: - // Format strings to replace \n - value = value.replaceAll("\n", "
"); - // Do not make the update if validation fails - try { - MarkupValidatorCopy.getInstance().validate(value); - } catch (Exception e) { - log.warn("Cannot set [" + value + "] on prop " + propName - + "of " + node + ", String cannot be validated - " - + e.getMessage()); - return; - } - // TODO check if the newly created property is of the correct type, - // otherwise the property will be silently created with a STRING - // property type. - node.setProperty(propName, value); - } catch (ValueFormatException vfe) { - log.warn("Cannot set [" + value + "] on prop " + propName + "of " - + node + " - " + vfe.getMessage()); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/forms/FormStyle.java b/org.argeo.cms/src/org/argeo/cms/forms/FormStyle.java deleted file mode 100644 index 1b9880d92..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/FormStyle.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.forms; - -/** Syles used */ -public enum FormStyle { - // Main - form, title, - // main part - header, headerBtn, headerCombo, section, sectionHeader, - // Property fields - propertyLabel, propertyText, propertyMessage, - // Date - popupCalendar, - // Buttons - starred, unstarred, starOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete, - // Contacts - email, address, phone, website, - // Social Media - facebook, twitter, linkedIn, instagram; - - public String style() { - return form.name() + '_' + name(); - } - - // TODO clean button style management - public final static String BUTTON_SUFFIX = "_btn"; -} diff --git a/org.argeo.cms/src/org/argeo/cms/forms/FormUtils.java b/org.argeo.cms/src/org/argeo/cms/forms/FormUtils.java deleted file mode 100644 index f8d08b08d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/forms/FormUtils.java +++ /dev/null @@ -1,197 +0,0 @@ -package org.argeo.cms.forms; - -import java.text.DateFormat; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsView; -import org.argeo.cms.util.CmsUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.fieldassist.ControlDecoration; -import org.eclipse.jface.fieldassist.FieldDecorationRegistry; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Utilitary methods to ease implementation of CMS forms */ -public class FormUtils { - private final static Log log = LogFactory.getLog(FormUtils.class); - - public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy"; - - /** Best effort to convert a String to a calendar. Fails silently */ - public static Calendar parseDate(DateFormat dateFormat, String calStr) { - Calendar cal = null; - if (EclipseUiUtils.notEmpty(calStr)) { - try { - Date date = dateFormat.parse(calStr); - cal = new GregorianCalendar(); - cal.setTime(date); - } catch (ParseException pe) { - // Silent - log.warn("Unable to parse date: " + calStr + " - msg: " - + pe.getMessage()); - } - } - return cal; - } - - /** Add a double click listener on tables that display a JCR node list */ - public static void addCanonicalDoubleClickListener(final TableViewer v) { - v.addDoubleClickListener(new IDoubleClickListener() { - - @Override - public void doubleClick(DoubleClickEvent event) { - CmsView cmsView = CmsUtils.getCmsView(); - Node node = (Node) ((IStructuredSelection) event.getSelection()) - .getFirstElement(); - try { - cmsView.navigateTo(node.getPath()); - } catch (RepositoryException e) { - throw new CmsException("Unable to get path for node " - + node + " before calling navigateTo(path)", e); - } - } - }); - } - - // MANAGE ERROR DECORATION - - public static ControlDecoration addDecoration(final Text text) { - final ControlDecoration dynDecoration = new ControlDecoration(text, - SWT.LEFT); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - dynDecoration.hide(); - return dynDecoration; - } - - public static void refreshDecoration(Text text, ControlDecoration deco, - boolean isValid, boolean clean) { - if (isValid || clean) { - text.setBackground(null); - deco.hide(); - } else { - text.setBackground(new Color(text.getDisplay(), 250, 200, 150)); - deco.show(); - } - } - - public static Image getDecorationImage(String image) { - FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault(); - return registry.getFieldDecoration(image).getImage(); - } - - public static void addCompulsoryDecoration(Label label) { - final ControlDecoration dynDecoration = new ControlDecoration(label, - SWT.RIGHT | SWT.TOP); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - } - - // TODO the read only generation of read only links for various contact type - // should be factorised in the cms Utils. - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - */ - public static String getPhoneLink(String value) { - return getPhoneLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - * - * @param value - * @param label - * a potentially distinct label - * @return - */ - public static String getPhoneLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - builder.append("").append(label) - .append(""); - return builder.toString(); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able mail - */ - public static String getMailLink(String value) { - return getMailLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able mail - * - * @param value - * @param label - * a potentially distinct label - * @return - */ - public static String getMailLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - value = replaceAmpersand(value); - builder.append("").append(label).append(""); - return builder.toString(); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able link - */ - public static String getUrlLink(String value) { - return getUrlLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able link - */ - public static String getUrlLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - value = replaceAmpersand(value); - label = replaceAmpersand(label); - if (!(value.startsWith("http://") || value.startsWith("https://"))) - value = "http://" + value; - builder.append("" + label + ""); - return builder.toString(); - } - - private static String AMPERSAND = "&"; - - /** - * Cleans a String by replacing any '&' by its HTML encoding '&' to - * avoid SAXParseException while rendering HTML with RWT - */ - public static String replaceAmpersand(String value) { - value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND); - return value; - } - - // Prevents instantiation - private FormUtils() { - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/i18n/LocaleUtils.java b/org.argeo.cms/src/org/argeo/cms/i18n/LocaleUtils.java index e422e21fe..6253e585b 100644 --- a/org.argeo.cms/src/org/argeo/cms/i18n/LocaleUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/i18n/LocaleUtils.java @@ -1,5 +1,7 @@ package org.argeo.cms.i18n; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.ResourceBundle; @@ -41,110 +43,27 @@ public class LocaleUtils { static Locale getCurrentLocale() { return UiContext.getLocale(); } - // private String id; - // private ClassLoader classLoader; - // private final Object defaultLocal; - // - // public Msg() { - // defaultLocal = null; - // } - // - // public Msg(Object defaultMessage) { - // this.defaultLocal = defaultMessage; - // } - // - // public String getId() { - // return id; - // } - // - // public void setId(String id) { - // this.id = id; - // } - // - // public ClassLoader getClassLoader() { - // return classLoader; - // } - // - // public void setClassLoader(ClassLoader classLoader) { - // this.classLoader = classLoader; - // } - // - // public Object getDefault() { - // return defaultLocal; - // } - // - // public String toString() { - // return local().toString(); - // } - // - // /** When used as the first word of a sentence. */ - // public String lead() { - // return lead(UiContext.getLocale()); - // } - // - // public String lead(Locale locale) { - // return lead(this, locale); - // } - // private static String lead(Msg msg, Locale locale) { - // String raw = msg.local(locale).toString(); - // return lead(raw, locale); - // } - - // public Object local() { - // Object local = local(this); - // if (local == null) - // local = getDefault(); - // if (local == null) - // throw new CmsException("No translation found for " + id); - // return local; - // } - // - // public Object local(Locale locale) { - // Object local = local(this, locale); - // if (local == null) - // local = getDefault(); - // if (local == null) - // throw new CmsException("No translation found for " + id); - // return local; - // } - // - // private static Object local(Msg msg) { - // Locale locale = UiContext.getLocale(); - // return local(msg, locale); - // } - // - // public static Object local(Msg msg, Locale locale) { - // String key = msg.getId(); - // int lastDot = key.lastIndexOf('.'); - // String className = key.substring(0, lastDot); - // String fieldName = key.substring(lastDot + 1); - // ResourceBundle rb = ResourceBundle.getBundle("/OSGI-INF/l10n/bundle", - // locale, msg.getClassLoader()); - // // ResourceBundle rb = ResourceBundle.getBundle(className, locale, - // // msg.getClassLoader()); - // return rb.getString(fieldName); - // } - - // public static void init(Class clss) { - // final Field[] fieldArray = clss.getDeclaredFields(); - // ClassLoader loader = clss.getClassLoader(); - // - // for (Field field : fieldArray) { - // if (Modifier.isStatic(field.getModifiers()) - // && field.getType().isAssignableFrom(Msg.class)) { - // try { - // Object obj = field.get(null); - // String id = clss.getCanonicalName() + "." + field.getName(); - // obj.getClass().getMethod("setId", String.class) - // .invoke(obj, id); - // obj.getClass() - // .getMethod("setClassLoader", ClassLoader.class) - // .invoke(obj, loader); - // } catch (Exception e) { - // throw new CmsException("Cannot prepare field " + field); - // } - // } - // } - // } + /** Returns null if argument is null. */ + public static List asLocaleList(Object locales) { + if (locales == null) + return null; + ArrayList availableLocales = new ArrayList(); + String[] codes = locales.toString().split(","); + for (int i = 0; i < codes.length; i++) { + String code = codes[i]; + // variant not supported + int indexUnd = code.indexOf("_"); + Locale locale; + if (indexUnd > 0) { + String language = code.substring(0, indexUnd); + String country = code.substring(indexUnd + 1); + locale = new Locale(language, country); + } else { + locale = new Locale(code); + } + availableLocales.add(locale); + } + return availableLocales; + } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/ImageManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/ImageManagerImpl.java deleted file mode 100644 index 122c24e35..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/ImageManagerImpl.java +++ /dev/null @@ -1,263 +0,0 @@ -package org.argeo.cms.internal; - -import static javax.jcr.Node.JCR_CONTENT; -import static javax.jcr.Property.JCR_DATA; -import static javax.jcr.nodetype.NodeType.NT_FILE; -import static javax.jcr.nodetype.NodeType.NT_RESOURCE; -import static org.argeo.cms.CmsConstants.NO_IMAGE_SIZE; -import static org.argeo.cms.CmsTypes.CMS_STYLED; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import javax.activation.MimetypesFileTypeMap; -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.CmsNames; -import org.argeo.cms.CmsTypes; -import org.argeo.cms.util.CmsUtils; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; - -/** Manages only public images so far. */ -public class ImageManagerImpl implements CmsImageManager, CmsNames { - private final static Log log = LogFactory.getLog(ImageManagerImpl.class); - private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); - - public Boolean load(Node node, Control control, Point preferredSize) - throws RepositoryException { - Point imageSize = getImageSize(node); - Point size; - String imgTag = null; - if (preferredSize == null || imageSize.x == 0 || imageSize.y == 0 - || (preferredSize.x == 0 && preferredSize.y == 0)) { - if (imageSize.x != 0 && imageSize.y != 0) { - // actual image size if completely known - size = imageSize; - } else { - // no image if not completely known - size = resizeTo(NO_IMAGE_SIZE, - preferredSize != null ? preferredSize : imageSize); - imgTag = CmsUtils.noImg(size); - } - - } else if (preferredSize.x != 0 && preferredSize.y != 0) { - // given size if completely provided - size = preferredSize; - } else { - // at this stage : - // image is completely known - assert imageSize.x != 0 && imageSize.y != 0; - // one and only one of the dimension as been specified - assert preferredSize.x == 0 || preferredSize.y == 0; - size = resizeTo(imageSize, preferredSize); - } - - boolean loaded = false; - if (control == null) - return loaded; - - if (control instanceof Label) { - if (imgTag == null) { - // IMAGE RETRIEVED HERE - imgTag = getImageTag(node, size); - // - if (imgTag == null) - imgTag = CmsUtils.noImg(size); - else - loaded = true; - } - - Label lbl = (Label) control; - lbl.setText(imgTag); - // lbl.setSize(size); - } else if (control instanceof FileUpload) { - FileUpload lbl = (FileUpload) control; - lbl.setImage(CmsUtils.noImage(size)); - lbl.setSize(size); - return loaded; - } else - loaded = false; - - return loaded; - } - - private Point resizeTo(Point orig, Point constraints) { - if (constraints.x != 0 && constraints.y != 0) { - return constraints; - } else if (constraints.x == 0 && constraints.y == 0) { - return orig; - } else if (constraints.y == 0) {// force width - return new Point(constraints.x, - scale(orig.y, orig.x, constraints.x)); - } else if (constraints.x == 0) {// force height - return new Point(scale(orig.x, orig.y, constraints.y), - constraints.y); - } - throw new CmsException("Cannot resize " + orig + " to " + constraints); - } - - private int scale(int origDimension, int otherDimension, int otherConstraint) { - return Math.round(origDimension - * divide(otherConstraint, otherDimension)); - } - - private float divide(int a, int b) { - return ((float) a) / ((float) b); - } - - public Point getImageSize(Node node) throws RepositoryException { - return new Point(node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node - .getProperty(CMS_IMAGE_WIDTH).getLong() : 0, - node.hasProperty(CMS_IMAGE_WIDTH) ? (int) node.getProperty( - CMS_IMAGE_HEIGHT).getLong() : 0); - } - - /** @return null if not available */ - @Override - public String getImageTag(Node node) throws RepositoryException { - return getImageTag(node, getImageSize(node)); - } - - private String getImageTag(Node node, Point size) - throws RepositoryException { - StringBuilder buf = getImageTagBuilder(node, size); - if (buf == null) - return null; - return buf.append("/>").toString(); - } - - /** @return null if not available */ - @Override - public StringBuilder getImageTagBuilder(Node node, Point size) - throws RepositoryException { - return getImageTagBuilder(node, Integer.toString(size.x), - Integer.toString(size.y)); - } - - /** @return null if not available */ - private StringBuilder getImageTagBuilder(Node node, String width, - String height) throws RepositoryException { - String url = getImageUrl(node); - if (url == null) - return null; - return CmsUtils.imgBuilder(url, width, height); - } - - /** @return null if not available */ - @Override - public String getImageUrl(Node node) throws RepositoryException { - return CmsUtils.getDataPath(node); - // String name = getResourceName(node); - // ResourceManager resourceManager = RWT.getResourceManager(); - // if (!resourceManager.isRegistered(name)) { - // InputStream inputStream = null; - // Binary binary = getImageBinary(node); - // if (binary == null) - // return null; - // try { - // inputStream = binary.getStream(); - // resourceManager.register(name, inputStream); - // } finally { - // IOUtils.closeQuietly(inputStream); - // JcrUtils.closeQuietly(binary); - // } - // if (log.isTraceEnabled()) - // log.trace("Registered image " + name); - // } - // return resourceManager.getLocation(name); - } - - protected String getResourceName(Node node) throws RepositoryException { - String workspace = node.getSession().getWorkspace().getName(); - if (node.hasNode(JCR_CONTENT)) - return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier(); - else - return workspace + '_' + node.getIdentifier(); - } - - public Binary getImageBinary(Node node) throws RepositoryException { - if (node.isNodeType(NT_FILE)) - return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary(); - else if (node.isNodeType(CMS_STYLED) && node.hasProperty(CMS_DATA)) { - return node.getProperty(CMS_DATA).getBinary(); - } else { - return null; - } - } - - public Image getSwtImage(Node node) throws RepositoryException { - InputStream inputStream = null; - Binary binary = getImageBinary(node); - if (binary == null) - return null; - try { - inputStream = binary.getStream(); - return new Image(Display.getCurrent(), inputStream); - } finally { - IOUtils.closeQuietly(inputStream); - JcrUtils.closeQuietly(binary); - } - } - - @Override - public String uploadImage(Node parentNode, String fileName, InputStream in) - throws RepositoryException { - InputStream inputStream = null; - try { - String previousResourceName = null; - if (parentNode.hasNode(fileName)) { - Node node = parentNode.getNode(fileName); - previousResourceName = getResourceName(node); - if (node.hasNode(JCR_CONTENT)) { - node.getNode(JCR_CONTENT).remove(); - node.addNode(JCR_CONTENT, NT_RESOURCE); - } - } - - byte[] arr = IOUtils.toByteArray(in); - Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr); - fileNode.addMixin(CmsTypes.CMS_IMAGE); - - inputStream = new ByteArrayInputStream(arr); - ImageData id = new ImageData(inputStream); - fileNode.setProperty(CMS_IMAGE_WIDTH, id.width); - fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height); - fileNode.setProperty(Property.JCR_MIMETYPE, - fileTypeMap.getContentType(fileName)); - fileNode.getSession().save(); - - // reset resource manager - ResourceManager resourceManager = RWT.getResourceManager(); - if (previousResourceName != null - && resourceManager.isRegistered(previousResourceName)) { - resourceManager.unregister(previousResourceName); - if (log.isDebugEnabled()) - log.debug("Unregistered image " + previousResourceName); - } - return getImageUrl(fileNode); - } catch (IOException e) { - throw new CmsException("Cannot upload image " + fileName + " in " - + parentNode, e); - } finally { - IOUtils.closeQuietly(inputStream); - } - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/JcrContentProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/JcrContentProvider.java deleted file mode 100644 index fd8eb2c75..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/JcrContentProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.internal; - -import java.util.ArrayList; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -@Deprecated -class JcrContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -1333678161322488674L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput == null) - return; - if (!(newInput instanceof Node)) - throw new CmsException("Input " + newInput + " must be a node"); - } - - @Override - public Object[] getElements(Object inputElement) { - try { - Node node = (Node) inputElement; - ArrayList arr = new ArrayList(); - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - arr.add(nit.nextNode()); - } - return arr.toArray(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public Object[] getChildren(Object parentElement) { - try { - Node node = (Node) parentElement; - ArrayList arr = new ArrayList(); - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - arr.add(nit.nextNode()); - } - return arr.toArray(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public Object getParent(Object element) { - try { - Node node = (Node) element; - if (node.getName().equals("")) - return null; - else - return node.getParent(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public boolean hasChildren(Object element) { - try { - Node node = (Node) element; - return node.hasNodes(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/JcrFileUploadReceiver.java b/org.argeo.cms/src/org/argeo/cms/internal/JcrFileUploadReceiver.java deleted file mode 100644 index a2ff49624..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/JcrFileUploadReceiver.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.internal; - -import static javax.jcr.nodetype.NodeType.NT_FILE; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.FilenameUtils; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.CmsNames; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadReceiver; - -public class JcrFileUploadReceiver extends FileUploadReceiver implements - CmsNames { - private final Node parentNode; - private final String nodeName; - private final CmsImageManager imageManager; - - /** If nodeName is null, use the uploaded file name */ - public JcrFileUploadReceiver(Node parentNode, String nodeName, - CmsImageManager imageManager) { - super(); - this.parentNode = parentNode; - this.nodeName = nodeName; - this.imageManager = imageManager; - } - - @Override - public void receive(InputStream stream, FileDetails details) - throws IOException { - try { - String fileName = nodeName != null ? nodeName : details - .getFileName(); - String contentType = details.getContentType(); - if (isImage(details.getFileName(), contentType)) { - imageManager.uploadImage(parentNode, fileName, stream); - return; - // InputStream inputStream = new ByteArrayInputStream(arr); - // ImageData id = new ImageData(inputStream); - // fileNode.addMixin(CmsTypes.CMS_IMAGE); - // fileNode.setProperty(CMS_IMAGE_WIDTH, id.width); - // fileNode.setProperty(CMS_IMAGE_HEIGHT, id.height); - } - - Node fileNode; - if (parentNode.hasNode(fileName)) { - fileNode = parentNode.getNode(fileName); - if (!fileNode.isNodeType(NT_FILE)) - fileNode.remove(); - } - fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream); - - if (contentType != null) { - fileNode.addMixin(NodeType.MIX_MIMETYPE); - fileNode.setProperty(Property.JCR_MIMETYPE, contentType); - } - processNewFile(fileNode); - fileNode.getSession().save(); - } catch (RepositoryException e) { - throw new CmsException("cannot receive " + details, e); - } - } - - protected Boolean isImage(String fileName, String contentType) { - String ext = FilenameUtils.getExtension(fileName); - return ext != null - && (ext.equals("png") || ext.equalsIgnoreCase("jpg")); - } - - protected void processNewFile(Node node) { - - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/SimpleEditableImage.java b/org.argeo.cms/src/org/argeo/cms/internal/SimpleEditableImage.java deleted file mode 100644 index 67e500ffc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/SimpleEditableImage.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.internal; - -import javax.jcr.RepositoryException; - -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.widgets.EditableImage; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Text; - -/** NOT working yet. */ -public class SimpleEditableImage extends EditableImage { - private static final long serialVersionUID = -5689145523114022890L; - - private String src; - private Point imageSize; - - public SimpleEditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - // load(getControl()); - getParent().layout(); - } - - public SimpleEditableImage(Composite parent, int swtStyle, String src, - Point imageSize) { - super(parent, swtStyle); - this.src = src; - this.imageSize = imageSize; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createText(box, style); - } else { - return createLabel(box, style); - } - } - - protected String createImgTag() throws RepositoryException { - String imgTag; - if (src != null) - imgTag = CmsUtils.img(src, imageSize); - else - imgTag = CmsUtils.noImg(imageSize != null ? imageSize - : NO_IMAGE_SIZE); - return imgTag; - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsUtils.style(text, style); - return text; - } - - public String getSrc() { - return src; - } - - public void setSrc(String src) { - this.src = src; - } - - public Point getImageSize() { - return imageSize; - } - - public void setImageSize(Point imageSize) { - this.imageSize = imageSize; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java index 44bc33159..4f1d3637b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java @@ -4,7 +4,6 @@ import java.io.Console; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; -import java.util.Locale; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -48,21 +47,22 @@ public class ConsoleCallbackHandler implements CallbackHandler { char[] answer = console.readPassword(); callback.setPassword(answer); Arrays.fill(answer, ' '); - } else if (callbacks[i] instanceof LocaleChoice) { - LocaleChoice callback = (LocaleChoice) callbacks[i]; - writer.write("Language"); - writer.write("\n"); - for (int j = 0; j < callback.getLocales().size(); j++) { - Locale locale = callback.getLocales().get(j); - writer.print(j + " : " + locale.getDisplayName() + "\n"); - } - writer.write("(" + callback.getDefaultIndex() + ") : "); - String answer = console.readLine(); - if (answer.trim().equals("")) - callback.setSelectedIndex(callback.getDefaultIndex()); - else - callback.setSelectedIndex(new Integer(answer.trim())); } +// else if (callbacks[i] instanceof LocaleChoice) { +// LocaleChoice callback = (LocaleChoice) callbacks[i]; +// writer.write("Language"); +// writer.write("\n"); +// for (int j = 0; j < callback.getLocales().size(); j++) { +// Locale locale = callback.getLocales().get(j); +// writer.print(j + " : " + locale.getDisplayName() + "\n"); +// } +// writer.write("(" + callback.getDefaultIndex() + ") : "); +// String answer = console.readLine(); +// if (answer.trim().equals("")) +// callback.setSelectedIndex(callback.getDefaultIndex()); +// else +// callback.setSelectedIndex(new Integer(answer.trim())); +// } } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java deleted file mode 100644 index a23348fac..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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. - */ -package org.argeo.cms.internal.auth; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.callback.LanguageCallback; - -import org.argeo.cms.CmsException; - -/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ -public class LocaleChoice { - private final List locales; - - private Integer selectedIndex = null; - private final Integer defaultIndex; - - public LocaleChoice(List locales, Locale defaultLocale) { - Integer defaultIndex = null; - this.locales = Collections.unmodifiableList(locales); - for (int i = 0; i < locales.size(); i++) - if (locales.get(i).equals(defaultLocale)) - defaultIndex = i; - - // based on language only - if (defaultIndex == null) - for (int i = 0; i < locales.size(); i++) - if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage())) - defaultIndex = i; - - if (defaultIndex == null) - throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales); - this.defaultIndex = defaultIndex; - - this.selectedIndex = defaultIndex; - } - - /** - * Convenience constructor based on a comma separated list of iso codes (en, - * en_US, fr_CA, etc.). Default selection is default locale. - */ - public LocaleChoice(String locales, Locale defaultLocale) { - this(asLocaleList(locales), defaultLocale); - } - - public String[] getSupportedLocalesLabels() { - String[] labels = new String[locales.size()]; - for (int i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - if (locale.getCountry().equals("")) - labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; - else - labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" - + locale.getLanguage() + "_" + locale.getCountry() + "]"; - - } - return labels; - } - - public Locale getSelectedLocale() { - if (selectedIndex == null) - return null; - return locales.get(selectedIndex); - } - - public void setSelectedIndex(Integer selectedIndex) { - this.selectedIndex = selectedIndex; - } - - public Integer getSelectedIndex() { - return selectedIndex; - } - - public Integer getDefaultIndex() { - return defaultIndex; - } - - public List getLocales() { - return locales; - } - - public Locale getDefaultLocale() { - return locales.get(getDefaultIndex()); - } - - /** Returns null if argument is null. */ - public static List asLocaleList(Object locales) { - if (locales == null) - return null; - ArrayList availableLocales = new ArrayList(); - String[] codes = locales.toString().split(","); - for (int i = 0; i < codes.length; i++) { - String code = codes[i]; - // variant not supported - int indexUnd = code.indexOf("_"); - Locale locale; - if (indexUnd > 0) { - String language = code.substring(0, indexUnd); - String country = code.substring(indexUnd + 1); - locale = new Locale(language, country); - } else { - locale = new Locale(code); - } - availableLocales.add(locale); - } - return availableLocales; - } - - public static void main(String[] args) { - for (String isoL : Locale.getISOLanguages()) { - Locale locale = new Locale(isoL); - System.out.println(isoL + "\t" + locale.getDisplayLanguage() + "\t" + locale.getDisplayLanguage(locale)); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/backup/RepositoryMigration.java b/org.argeo.cms/src/org/argeo/cms/internal/backup/RepositoryMigration.java index 82f6fef5b..d708f90cb 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/backup/RepositoryMigration.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/backup/RepositoryMigration.java @@ -8,7 +8,7 @@ import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; -import org.argeo.cms.maintenance.DataMigration; +import org.argeo.cms.DataMigration; import org.argeo.jcr.JcrUtils; /** Migrate data between two workspaces, at JCR level. */ diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java index 63aeeac29..7906c28fa 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java @@ -3,7 +3,6 @@ package org.argeo.cms.internal.kernel; import static bitronix.tm.TransactionManagerServices.getTransactionManager; import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry; import static java.util.Locale.ENGLISH; -import static org.argeo.cms.internal.auth.LocaleChoice.asLocaleList; import java.io.File; import java.net.InetAddress; @@ -23,11 +22,10 @@ import javax.transaction.UserTransaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.auth.AuthConstants; -import org.argeo.cms.maintenance.MaintenanceUi; +import org.argeo.cms.i18n.LocaleUtils; import org.argeo.node.NodeConstants; import org.argeo.node.NodeState; import org.argeo.util.LangUtils; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; @@ -85,7 +83,7 @@ public class CmsState implements NodeState { Object defaultLocaleValue = KernelUtils.getFrameworkProp(NodeConstants.I18N_DEFAULT_LOCALE); defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString()) : new Locale(ENGLISH.getLanguage()); - locales = asLocaleList(KernelUtils.getFrameworkProp(NodeConstants.I18N_LOCALES)); + locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(NodeConstants.I18N_LOCALES)); } private void initServices() { @@ -107,12 +105,6 @@ public class CmsState implements NodeState { Dictionary props = new Hashtable<>(); props.put(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID); bc.registerService(ManagedServiceFactory.class, userAdmin, props); - - // UI - bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), - LangUtils.init(KernelConstants.CONTEXT_NAME_PROP, "system")); - bc.registerService(ApplicationConfiguration.class, new UserUi(), - LangUtils.init(KernelConstants.CONTEXT_NAME_PROP, "user")); } private void initTransactionManager() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index cb22e2790..50f7ef322 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -1,9 +1,14 @@ package org.argeo.cms.internal.kernel; +import static org.argeo.cms.internal.kernel.KernelConstants.WEBDAV_PRIVATE; +import static org.argeo.cms.internal.kernel.KernelConstants.WEBDAV_PUBLIC; + import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.security.PrivilegedAction; @@ -13,6 +18,7 @@ import java.util.Hashtable; import java.util.Properties; import java.util.TreeSet; +import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; @@ -24,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.argeo.cms.CmsException; import org.argeo.cms.auth.AuthConstants; +import org.argeo.jcr.ArgeoJcrConstants; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; @@ -207,6 +214,59 @@ class KernelUtils implements KernelConstants { } } + // DATA + public static StringBuilder getServerBaseUrl(HttpServletRequest request) { + try { + URL url = new URL(request.getRequestURL().toString()); + StringBuilder buf = new StringBuilder(); + buf.append(url.getProtocol()).append("://").append(url.getHost()); + if (url.getPort() != -1) + buf.append(':').append(url.getPort()); + return buf; + } catch (MalformedURLException e) { + throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); + } + } + + public static String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append(getDataPath(node)); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + } + + public static String getDataPath(Node node) throws RepositoryException { + assert node != null; + String userId = node.getSession().getUserID(); +// if (log.isTraceEnabled()) +// log.trace(userId + " : " + node.getPath()); + StringBuilder buf = new StringBuilder(); + boolean isAnonymous = userId.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS); + if (isAnonymous) + buf.append(WEBDAV_PUBLIC); + else + buf.append(WEBDAV_PRIVATE); + // TODO convey repo alias vie repository properties + return buf.append('/').append(ArgeoJcrConstants.ALIAS_NODE).append('/').append(node.getSession().getWorkspace().getName()) + .append(node.getPath()).toString(); + } + + public static String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append('/').append('!').append(node.getPath()); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + // return request.getRequestURL().append('!').append(node.getPath()) + // .toString(); + } + + private KernelUtils() { } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java index 97e912edd..4a6ad2337 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -29,7 +29,6 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; -import org.argeo.cms.util.CmsUtils; import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.JcrUtils; import org.osgi.framework.BundleContext; @@ -128,13 +127,13 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null; Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate() : null; - String url = CmsUtils.getCanonicalUrl(node, request); + String url = KernelUtils.getCanonicalUrl(node, request); String imgUrl = null; loop: for (NodeIterator it = node.getNodes(); it.hasNext();) { // Takes the first found cms:image Node child = it.nextNode(); if (child.isNodeType(CMS_IMAGE)) { - imgUrl = CmsUtils.getDataUrl(child, request); + imgUrl = KernelUtils.getDataUrl(child, request); break loop; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/UserUi.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/UserUi.java deleted file mode 100644 index fd332eee1..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/UserUi.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import org.argeo.cms.util.LoginEntryPoint; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; - -public class UserUi implements ApplicationConfiguration { - @Override - public void configure(Application application) { - application.setOperationMode(OperationMode.SWT_COMPATIBILITY); - application.addEntryPoint("/login", LoginEntryPoint.class, null); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/text/AbstractTextViewer.java b/org.argeo.cms/src/org/argeo/cms/internal/text/AbstractTextViewer.java deleted file mode 100644 index cb0b3a974..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/text/AbstractTextViewer.java +++ /dev/null @@ -1,892 +0,0 @@ -package org.argeo.cms.internal.text; - -import static javax.jcr.Property.JCR_TITLE; -import static org.argeo.cms.util.CmsUtils.fillWidth; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Map; -import java.util.Observer; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.CmsNames; -import org.argeo.cms.CmsTypes; -import org.argeo.cms.text.Img; -import org.argeo.cms.text.Paragraph; -import org.argeo.cms.text.TextInterpreter; -import org.argeo.cms.text.TextSection; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.AbstractPageViewer; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.viewers.NodePart; -import org.argeo.cms.viewers.PropertyPart; -import org.argeo.cms.viewers.Section; -import org.argeo.cms.viewers.SectionPart; -import org.argeo.cms.widgets.EditableImage; -import org.argeo.cms.widgets.EditableText; -import org.argeo.cms.widgets.StyledControl; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadEvent; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Text; - -/** Base class for text viewers and editors. */ -public abstract class AbstractTextViewer extends AbstractPageViewer implements - CmsNames, KeyListener, Observer { - private static final long serialVersionUID = -2401274679492339668L; - private final static Log log = LogFactory.getLog(AbstractTextViewer.class); - - private final Section mainSection; - - private TextInterpreter textInterpreter = new TextInterpreterImpl(); - private CmsImageManager imageManager = CmsUtils.getCmsView() - .getImageManager(); - - private FileUploadListener fileUploadListener; - private TextContextMenu styledTools; - - private final boolean flat; - - protected AbstractTextViewer(Section parent, int style, - CmsEditable cmsEditable) { - super(parent, style, cmsEditable); - flat = SWT.FLAT == (style & SWT.FLAT); - - if (getCmsEditable().canEdit()) { - fileUploadListener = new FUL(); - styledTools = new TextContextMenu(this, parent.getDisplay()); - } - this.mainSection = parent; - initModelIfNeeded(mainSection.getNode()); - // layout(this.mainSection); - } - - @Override - public Control getControl() { - return mainSection; - } - - protected void refresh(Control control) throws RepositoryException { - if (!(control instanceof Section)) - return; - Section section = (Section) control; - if (section instanceof TextSection) { - CmsUtils.clear(section); - Node node = section.getNode(); - TextSection textSection = (TextSection) section; - if (node.hasProperty(Property.JCR_TITLE)) { - if (section.getHeader() == null) - section.createHeader(); - if (node.hasProperty(Property.JCR_TITLE)) { - SectionTitle title = newSectionTitle(textSection, node); - title.setLayoutData(CmsUtils.fillWidth()); - updateContent(title); - } - } - - for (NodeIterator ni = node.getNodes(CMS_P); ni.hasNext();) { - Node child = ni.nextNode(); - final SectionPart sectionPart; - if (child.isNodeType(CmsTypes.CMS_IMAGE) - || child.isNodeType(NodeType.NT_FILE)) { - sectionPart = newImg(textSection, child); - } else if (child.isNodeType(CmsTypes.CMS_STYLED)) { - sectionPart = newParagraph(textSection, child); - } else { - sectionPart = newSectionPart(textSection, child); - if (sectionPart == null) - throw new CmsException("Unsupported node " + child); - // TODO list node types in exception - } - if (sectionPart instanceof Control) - ((Control) sectionPart).setLayoutData(CmsUtils.fillWidth()); - } - - if (!flat) - for (NodeIterator ni = section.getNode().getNodes(CMS_H); ni - .hasNext();) { - Node child = ni.nextNode(); - if (child.isNodeType(CmsTypes.CMS_SECTION)) { - TextSection newSection = new TextSection(section, - SWT.NONE, child); - newSection.setLayoutData(CmsUtils.fillWidth()); - refresh(newSection); - } - } - } else { - for (Section s : section.getSubSections().values()) - refresh(s); - } - // section.layout(); - } - - /** To be overridden in order to provide additional SectionPart types */ - protected SectionPart newSectionPart(TextSection textSection, Node node) { - return null; - } - - // CRUD - protected Paragraph newParagraph(TextSection parent, Node node) - throws RepositoryException { - Paragraph paragraph = new Paragraph(parent, parent.getStyle(), node); - updateContent(paragraph); - paragraph.setLayoutData(fillWidth()); - paragraph.setMouseListener(getMouseListener()); - return paragraph; - } - - protected Img newImg(TextSection parent, Node node) - throws RepositoryException { - Img img = new Img(parent, parent.getStyle(), node) { - private static final long serialVersionUID = 1297900641952417540L; - - @Override - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, - SWT.DEFAULT)); - } - - @Override - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, - SWT.DEFAULT)); - } - }; - img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); - updateContent(img); - img.setMouseListener(getMouseListener()); - return img; - } - - protected SectionTitle newSectionTitle(TextSection parent, Node node) - throws RepositoryException { - SectionTitle title = new SectionTitle(parent.getHeader(), - parent.getStyle(), node.getProperty(JCR_TITLE)); - updateContent(title); - title.setMouseListener(getMouseListener()); - return title; - } - - protected SectionTitle prepareSectionTitle(Section newSection, - String titleText) throws RepositoryException { - Node sectionNode = newSection.getNode(); - if (!sectionNode.hasProperty(JCR_TITLE)) - sectionNode.setProperty(Property.JCR_TITLE, ""); - getTextInterpreter().write(sectionNode.getProperty(Property.JCR_TITLE), - titleText); - if (newSection.getHeader() == null) - newSection.createHeader(); - SectionTitle sectionTitle = newSectionTitle((TextSection) newSection, - sectionNode); - return sectionTitle; - } - - protected void updateContent(EditablePart part) throws RepositoryException { - if (part instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) part; - Node partNode = sectionPart.getNode(); - - if (part instanceof StyledControl - && (sectionPart.getSection() instanceof TextSection)) { - TextSection section = (TextSection) sectionPart.getSection(); - StyledControl styledControl = (StyledControl) part; - if (partNode.isNodeType(CmsTypes.CMS_STYLED)) { - String style = partNode.hasProperty(CMS_STYLE) ? partNode - .getProperty(CMS_STYLE).getString() : section - .getDefaultTextStyle(); - styledControl.setStyle(style); - } - } - // use control AFTER setting style, since it may have been reset - - if (part instanceof EditableText) { - EditableText paragraph = (EditableText) part; - if (paragraph == getEdited()) - paragraph.setText(textInterpreter.read(partNode)); - else - paragraph.setText(textInterpreter.raw(partNode)); - } else if (part instanceof EditableImage) { - EditableImage editableImage = (EditableImage) part; - imageManager.load(partNode, part.getControl(), - editableImage.getPreferredImageSize()); - } - } else if (part instanceof SectionTitle) { - SectionTitle title = (SectionTitle) part; - title.setStyle(title.getSection().getTitleStyle()); - // use control AFTER setting style - if (title == getEdited()) - title.setText(textInterpreter.read(title.getProperty())); - else - title.setText(textInterpreter.raw(title.getProperty())); - } - } - - // OVERRIDDEN FROM PARENT VIEWER - @Override - protected void save(EditablePart part) throws RepositoryException { - if (part instanceof EditableText) { - EditableText et = (EditableText) part; - String text = ((Text) et.getControl()).getText(); - - String[] lines = text.split("[\r\n]+"); - assert lines.length != 0; - saveLine(part, lines[0]); - if (lines.length > 1) { - ArrayList toLayout = new ArrayList(); - if (part instanceof Paragraph) { - Paragraph currentParagraph = (Paragraph) et; - Section section = currentParagraph.getSection(); - Node sectionNode = section.getNode(); - Node currentParagraphN = currentParagraph.getNode(); - for (int i = 1; i < lines.length; i++) { - Node newNode = sectionNode.addNode(CMS_P); - newNode.addMixin(CmsTypes.CMS_STYLED); - saveLine(newNode, lines[i]); - // second node was create as last, if it is not the next - // one, it - // means there are some in between and we can take the - // one at - // index+1 for the re-order - if (newNode.getIndex() > currentParagraphN.getIndex() + 1) { - sectionNode.orderBefore(p(newNode.getIndex()), - p(currentParagraphN.getIndex() + 1)); - } - Paragraph newParagraph = newParagraph( - (TextSection) section, newNode); - newParagraph.moveBelow(currentParagraph); - toLayout.add(newParagraph); - - currentParagraph = newParagraph; - currentParagraphN = newNode; - } - persistChanges(sectionNode); - } - // TODO or rather return the created paragarphs? - layout(toLayout.toArray(new Control[toLayout.size()])); - } - } - } - - protected void saveLine(EditablePart part, String line) { - if (part instanceof NodePart) { - saveLine(((NodePart) part).getNode(), line); - } else if (part instanceof PropertyPart) { - saveLine(((PropertyPart) part).getProperty(), line); - } else { - throw new CmsException("Unsupported part " + part); - } - } - - protected void saveLine(Item item, String line) { - line = line.trim(); - textInterpreter.write(item, line); - } - - @Override - protected void prepare(EditablePart part, Object caretPosition) { - Control control = part.getControl(); - if (control instanceof Text) { - Text text = (Text) control; - if (caretPosition != null) - if (caretPosition instanceof Integer) - text.setSelection((Integer) caretPosition); - else if (caretPosition instanceof Point) { - // TODO find a way to position the caret at the right place - } - text.setData(RWT.ACTIVE_KEYS, new String[] { "BACKSPACE", "ESC", - "TAB", "SHIFT+TAB", "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT", - "ALT+ARROW_UP", "ALT+ARROW_DOWN", "RETURN", "CTRL+RETURN", - "ENTER", "DELETE" }); - text.setData(RWT.CANCEL_KEYS, new String[] { "RETURN", - "ALT+ARROW_LEFT", "ALT+ARROW_RIGHT" }); - text.addKeyListener(this); - } else if (part instanceof Img) { - ((Img) part).setFileUploadListener(fileUploadListener); - } - } - - // REQUIRED BY CONTEXT MENU - void setParagraphStyle(Paragraph paragraph, String style) { - try { - Node paragraphNode = paragraph.getNode(); - paragraphNode.setProperty(CMS_STYLE, style); - persistChanges(paragraphNode); - updateContent(paragraph); - layout(paragraph); - } catch (RepositoryException e1) { - throw new CmsException("Cannot set style " + style + " on " - + paragraph, e1); - } - } - - void deletePart(SectionPart paragraph) { - try { - Node paragraphNode = paragraph.getNode(); - Section section = paragraph.getSection(); - Session session = paragraphNode.getSession(); - paragraphNode.remove(); - session.save(); - if (paragraph instanceof Control) - ((Control) paragraph).dispose(); - layout(section); - } catch (RepositoryException e1) { - throw new CmsException("Cannot delete " + paragraph, e1); - } - } - - String getRawParagraphText(Paragraph paragraph) { - return textInterpreter.raw(paragraph.getNode()); - } - - // COMMANDS - protected void splitEdit() { - checkEdited(); - try { - if (getEdited() instanceof Paragraph) { - Paragraph paragraph = (Paragraph) getEdited(); - Text text = (Text) paragraph.getControl(); - int caretPosition = text.getCaretPosition(); - String txt = text.getText(); - String first = txt.substring(0, caretPosition); - String second = txt.substring(caretPosition); - Node firstNode = paragraph.getNode(); - Node sectionNode = firstNode.getParent(); - firstNode.setProperty(CMS_CONTENT, first); - Node secondNode = sectionNode.addNode(CMS_P); - secondNode.addMixin(CmsTypes.CMS_STYLED); - // second node was create as last, if it is not the next one, it - // means there are some in between and we can take the one at - // index+1 for the re-order - if (secondNode.getIndex() > firstNode.getIndex() + 1) { - sectionNode.orderBefore(p(secondNode.getIndex()), - p(firstNode.getIndex() + 1)); - } - - // if we die in between, at least we still have the whole text - // in the first node - try { - textInterpreter.write(secondNode, second); - textInterpreter.write(firstNode, first); - } catch (Exception e) { - // so that no additional nodes are created: - JcrUtils.discardUnderlyingSessionQuietly(firstNode); - throw e; - } - - persistChanges(firstNode); - - Paragraph secondParagraph = paragraphSplitted(paragraph, - secondNode); - edit(secondParagraph, 0); - } else if (getEdited() instanceof SectionTitle) { - SectionTitle sectionTitle = (SectionTitle) getEdited(); - Text text = (Text) sectionTitle.getControl(); - String txt = text.getText(); - int caretPosition = text.getCaretPosition(); - Section section = sectionTitle.getSection(); - Node sectionNode = section.getNode(); - Node paragraphNode = sectionNode.addNode(CMS_P); - paragraphNode.addMixin(CmsTypes.CMS_STYLED); - textInterpreter.write(paragraphNode, - txt.substring(caretPosition)); - textInterpreter.write( - sectionNode.getProperty(Property.JCR_TITLE), - txt.substring(0, caretPosition)); - sectionNode.orderBefore(p(paragraphNode.getIndex()), p(1)); - persistChanges(sectionNode); - - Paragraph paragraph = sectionTitleSplitted(sectionTitle, - paragraphNode); - // section.layout(); - edit(paragraph, 0); - } - } catch (RepositoryException e) { - throw new CmsException("Cannot split " + getEdited(), e); - } - } - - protected void mergeWithPrevious() { - checkEdited(); - try { - Paragraph paragraph = (Paragraph) getEdited(); - Text text = (Text) paragraph.getControl(); - String txt = text.getText(); - Node paragraphNode = paragraph.getNode(); - if (paragraphNode.getIndex() == 1) - return;// do nothing - Node sectionNode = paragraphNode.getParent(); - Node previousNode = sectionNode - .getNode(p(paragraphNode.getIndex() - 1)); - String previousTxt = textInterpreter.read(previousNode); - textInterpreter.write(previousNode, previousTxt + txt); - paragraphNode.remove(); - persistChanges(sectionNode); - - Paragraph previousParagraph = paragraphMergedWithPrevious( - paragraph, previousNode); - edit(previousParagraph, previousTxt.length()); - } catch (RepositoryException e) { - throw new CmsException("Cannot stop editing", e); - } - } - - protected void mergeWithNext() { - checkEdited(); - try { - Paragraph paragraph = (Paragraph) getEdited(); - Text text = (Text) paragraph.getControl(); - String txt = text.getText(); - Node paragraphNode = paragraph.getNode(); - Node sectionNode = paragraphNode.getParent(); - NodeIterator paragraphNodes = sectionNode.getNodes(CMS_P); - long size = paragraphNodes.getSize(); - if (paragraphNode.getIndex() == size) - return;// do nothing - Node nextNode = sectionNode - .getNode(p(paragraphNode.getIndex() + 1)); - String nextTxt = textInterpreter.read(nextNode); - textInterpreter.write(paragraphNode, txt + nextTxt); - - Section section = paragraph.getSection(); - Paragraph removed = (Paragraph) section.getSectionPart(nextNode - .getIdentifier()); - - nextNode.remove(); - persistChanges(sectionNode); - - paragraphMergedWithNext(paragraph, removed); - edit(paragraph, txt.length()); - } catch (RepositoryException e) { - throw new CmsException("Cannot stop editing", e); - } - } - - protected synchronized void upload(EditablePart part) { - try { - if (part instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) part; - Node partNode = sectionPart.getNode(); - int partIndex = partNode.getIndex(); - Section section = sectionPart.getSection(); - Node sectionNode = section.getNode(); - - if (part instanceof Paragraph) { - Node newNode = sectionNode.addNode(CMS_P, NodeType.NT_FILE); - newNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); - JcrUtils.copyBytesAsFile(sectionNode, - p(newNode.getIndex()), new byte[0]); - if (partIndex < newNode.getIndex() - 1) { - // was not last - sectionNode.orderBefore(p(newNode.getIndex()), - p(partIndex - 1)); - } - // sectionNode.orderBefore(p(partNode.getIndex()), - // p(newNode.getIndex())); - persistChanges(sectionNode); - Img img = newImg((TextSection) section, newNode); - edit(img, null); - layout(img.getControl()); - } else if (part instanceof Img) { - if (getEdited() == part) - return; - edit(part, null); - layout(part.getControl()); - } - } - } catch (RepositoryException e) { - throw new CmsException("Cannot upload", e); - } - } - - protected void deepen() { - if (flat) - return; - checkEdited(); - try { - if (getEdited() instanceof Paragraph) { - Paragraph paragraph = (Paragraph) getEdited(); - Text text = (Text) paragraph.getControl(); - String txt = text.getText(); - Node paragraphNode = paragraph.getNode(); - Section section = paragraph.getSection(); - Node sectionNode = section.getNode(); - // main title - if (section == mainSection && section instanceof TextSection - && paragraphNode.getIndex() == 1 - && !sectionNode.hasProperty(JCR_TITLE)) { - SectionTitle sectionTitle = prepareSectionTitle(section, - txt); - edit(sectionTitle, 0); - return; - } - Node newSectionNode = sectionNode.addNode(CMS_H, - CmsTypes.CMS_SECTION); - sectionNode.orderBefore(h(newSectionNode.getIndex()), h(1)); - - int paragraphIndex = paragraphNode.getIndex(); - String sectionPath = sectionNode.getPath(); - String newSectionPath = newSectionNode.getPath(); - while (sectionNode.hasNode(p(paragraphIndex + 1))) { - Node parag = sectionNode.getNode(p(paragraphIndex + 1)); - sectionNode.getSession().move( - sectionPath + '/' + p(paragraphIndex + 1), - newSectionPath + '/' + CMS_P); - SectionPart sp = section.getSectionPart(parag - .getIdentifier()); - if (sp instanceof Control) - ((Control) sp).dispose(); - } - // create property - newSectionNode.setProperty(Property.JCR_TITLE, ""); - getTextInterpreter().write( - newSectionNode.getProperty(Property.JCR_TITLE), txt); - - TextSection newSection = new TextSection(section, - section.getStyle(), newSectionNode); - newSection.setLayoutData(CmsUtils.fillWidth()); - newSection.moveBelow(paragraph); - - // dispose - paragraphNode.remove(); - paragraph.dispose(); - - refresh(newSection); - newSection.getParent().layout(); - layout(newSection); - persistChanges(sectionNode); - } else if (getEdited() instanceof SectionTitle) { - SectionTitle sectionTitle = (SectionTitle) getEdited(); - Section section = sectionTitle.getSection(); - Section parentSection = section.getParentSection(); - if (parentSection == null) - return;// cannot deepen main section - Node sectionN = section.getNode(); - Node parentSectionN = parentSection.getNode(); - if (sectionN.getIndex() == 1) - return;// cannot deepen first section - Node previousSectionN = parentSectionN.getNode(h(sectionN - .getIndex() - 1)); - NodeIterator subSections = previousSectionN.getNodes(CMS_H); - int subsectionsCount = (int) subSections.getSize(); - previousSectionN.getSession().move( - sectionN.getPath(), - previousSectionN.getPath() + "/" - + h(subsectionsCount + 1)); - section.dispose(); - TextSection newSection = new TextSection(section, - section.getStyle(), sectionN); - refresh(newSection); - persistChanges(previousSectionN); - } - } catch (RepositoryException e) { - throw new CmsException("Cannot deepen " + getEdited(), e); - } - } - - protected void undeepen() { - if (flat) - return; - checkEdited(); - try { - if (getEdited() instanceof Paragraph) { - upload(getEdited()); - } else if (getEdited() instanceof SectionTitle) { - SectionTitle sectionTitle = (SectionTitle) getEdited(); - Section section = sectionTitle.getSection(); - Node sectionNode = section.getNode(); - Section parentSection = section.getParentSection(); - if (parentSection == null) - return;// cannot undeepen main section - - // choose in which section to merge - Section mergedSection; - if (sectionNode.getIndex() == 1) - mergedSection = section.getParentSection(); - else { - Map parentSubsections = parentSection - .getSubSections(); - ArrayList
lst = new ArrayList
( - parentSubsections.values()); - mergedSection = lst.get(sectionNode.getIndex() - 1); - } - Node mergedNode = mergedSection.getNode(); - boolean mergedHasSubSections = mergedNode.hasNode(CMS_H); - - // title as paragraph - Node newParagrapheNode = mergedNode.addNode(CMS_P); - newParagrapheNode.addMixin(CmsTypes.CMS_STYLED); - if (mergedHasSubSections) - mergedNode.orderBefore(p(newParagrapheNode.getIndex()), - h(1)); - String txt = getTextInterpreter().read( - sectionNode.getProperty(Property.JCR_TITLE)); - getTextInterpreter().write(newParagrapheNode, txt); - // move - NodeIterator paragraphs = sectionNode.getNodes(CMS_P); - while (paragraphs.hasNext()) { - Node p = paragraphs.nextNode(); - SectionPart sp = section.getSectionPart(p.getIdentifier()); - if (sp instanceof Control) - ((Control) sp).dispose(); - mergedNode.getSession().move(p.getPath(), - mergedNode.getPath() + '/' + CMS_P); - if (mergedHasSubSections) - mergedNode.orderBefore(p(p.getIndex()), h(1)); - } - - Iterator
subsections = section.getSubSections() - .values().iterator(); - // NodeIterator sections = sectionNode.getNodes(CMS_H); - while (subsections.hasNext()) { - Section subsection = subsections.next(); - Node s = subsection.getNode(); - mergedNode.getSession().move(s.getPath(), - mergedNode.getPath() + '/' + CMS_H); - subsection.dispose(); - } - - // remove section - section.getNode().remove(); - section.dispose(); - - refresh(mergedSection); - mergedSection.getParent().layout(); - layout(mergedSection); - persistChanges(mergedNode); - } - } catch (RepositoryException e) { - throw new CmsException("Cannot undeepen " + getEdited(), e); - } - } - - // UI CHANGES - protected Paragraph paragraphSplitted(Paragraph paragraph, Node newNode) - throws RepositoryException { - Section section = paragraph.getSection(); - updateContent(paragraph); - Paragraph newParagraph = newParagraph((TextSection) section, newNode); - newParagraph.setLayoutData(CmsUtils.fillWidth()); - newParagraph.moveBelow(paragraph); - layout(paragraph.getControl(), newParagraph.getControl()); - return newParagraph; - } - - protected Paragraph sectionTitleSplitted(SectionTitle sectionTitle, - Node newNode) throws RepositoryException { - updateContent(sectionTitle); - Paragraph newParagraph = newParagraph(sectionTitle.getSection(), - newNode); - // we assume beforeFirst is not null since there was a sectionTitle - newParagraph.moveBelow(sectionTitle.getSection().getHeader()); - layout(sectionTitle.getControl(), newParagraph.getControl()); - return newParagraph; - } - - protected Paragraph paragraphMergedWithPrevious(Paragraph removed, - Node remaining) throws RepositoryException { - Section section = removed.getSection(); - removed.dispose(); - - Paragraph paragraph = (Paragraph) section.getSectionPart(remaining - .getIdentifier()); - updateContent(paragraph); - layout(paragraph.getControl()); - return paragraph; - } - - protected void paragraphMergedWithNext(Paragraph remaining, - Paragraph removed) throws RepositoryException { - removed.dispose(); - updateContent(remaining); - layout(remaining.getControl()); - } - - // UTILITIES - protected String p(Integer index) { - StringBuilder sb = new StringBuilder(6); - sb.append(CMS_P).append('[').append(index).append(']'); - return sb.toString(); - } - - protected String h(Integer index) { - StringBuilder sb = new StringBuilder(5); - sb.append(CMS_H).append('[').append(index).append(']'); - return sb.toString(); - } - - // GETTERS / SETTERS - public Section getMainSection() { - return mainSection; - } - - public boolean isFlat() { - return flat; - } - - public TextInterpreter getTextInterpreter() { - return textInterpreter; - } - - // KEY LISTENER - @Override - public void keyPressed(KeyEvent ke) { - if (log.isTraceEnabled()) - log.trace(ke); - - if (getEdited() == null) - return; - boolean altPressed = (ke.stateMask & SWT.ALT) != 0; - boolean shiftPressed = (ke.stateMask & SWT.SHIFT) != 0; - boolean ctrlPressed = (ke.stateMask & SWT.CTRL) != 0; - - try { - // Common - if (ke.keyCode == SWT.ESC) { - cancelEdit(); - } else if (ke.character == '\r') { - splitEdit(); - } else if (ke.character == 'S') { - if (ctrlPressed) - saveEdit(); - } else if (ke.character == '\t') { - if (!shiftPressed) { - deepen(); - } else if (shiftPressed) { - undeepen(); - } - } else { - if (getEdited() instanceof Paragraph) { - Paragraph paragraph = (Paragraph) getEdited(); - Section section = paragraph.getSection(); - if (altPressed && ke.keyCode == SWT.ARROW_RIGHT) { - edit(section.nextSectionPart(paragraph), 0); - } else if (altPressed && ke.keyCode == SWT.ARROW_LEFT) { - edit(section.previousSectionPart(paragraph), 0); - } else if (ke.character == SWT.BS) { - Text text = (Text) paragraph.getControl(); - int caretPosition = text.getCaretPosition(); - if (caretPosition == 0) { - mergeWithPrevious(); - } - } else if (ke.character == SWT.DEL) { - Text text = (Text) paragraph.getControl(); - int caretPosition = text.getCaretPosition(); - int charcount = text.getCharCount(); - if (caretPosition == charcount) { - mergeWithNext(); - } - } - } - } - } catch (Exception e) { - ke.doit = false; - notifyEditionException(e); - } - } - - @Override - public void keyReleased(KeyEvent e) { - } - - // MOUSE LISTENER - @Override - protected MouseListener createMouseListener() { - return new ML(); - } - - private class ML extends MouseAdapter { - private static final long serialVersionUID = 8526890859876770905L; - - @Override - public void mouseDoubleClick(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (getCmsEditable().canEdit()) { - if (getCmsEditable().isEditing() - && !(getEdited() instanceof Img)) { - if (source == mainSection) - return; - EditablePart part = findDataParent(source); - upload(part); - } else { - getCmsEditable().startEditing(); - } - } - } - } - - @Override - public void mouseDown(MouseEvent e) { - if (getCmsEditable().isEditing()) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - EditablePart composite = findDataParent(source); - Point point = new Point(e.x, e.y); - if (!(composite instanceof Img)) - edit(composite, source.toDisplay(point)); - } else if (e.button == 3) { - EditablePart composite = findDataParent((Control) e - .getSource()); - if (styledTools != null) - styledTools.show(composite, new Point(e.x, e.y)); - } - } - } - - @Override - public void mouseUp(MouseEvent e) { - } - } - - // FILE UPLOAD LISTENER - private class FUL implements FileUploadListener { - public void uploadProgress(FileUploadEvent event) { - // TODO Monitor upload progress - } - - public void uploadFailed(FileUploadEvent event) { - throw new CmsException("Upload failed " + event, - event.getException()); - } - - public void uploadFinished(FileUploadEvent event) { - for (FileDetails file : event.getFileDetails()) { - if (log.isDebugEnabled()) - log.debug("Received: " + file.getFileName()); - } - mainSection.getDisplay().syncExec(new Runnable() { - @Override - public void run() { - saveEdit(); - } - }); - FileUploadHandler uploadHandler = (FileUploadHandler) event - .getSource(); - uploadHandler.dispose(); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/internal/text/MarkupValidatorCopy.java b/org.argeo.cms/src/org/argeo/cms/internal/text/MarkupValidatorCopy.java deleted file mode 100644 index e28d3704c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/text/MarkupValidatorCopy.java +++ /dev/null @@ -1,184 +0,0 @@ -package org.argeo.cms.internal.text; - -import java.io.StringReader; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.argeo.cms.forms.FormPageViewer; -import org.eclipse.rap.rwt.SingletonUtil; -import org.eclipse.swt.widgets.Widget; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.helpers.DefaultHandler; - -/** - * Copy of RAP v2.3 since it is in an internal package. - * - * FIXME made public to enable validation from the {@link FormPageViewer} - */ -public class MarkupValidatorCopy { - - // Used by Eclipse Scout project - public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled"; - - private static final String DTD = createDTD(); - private static final Map SUPPORTED_ELEMENTS = createSupportedElementsMap(); - private final SAXParser saxParser; - - public static MarkupValidatorCopy getInstance() { - return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class); - } - - public MarkupValidatorCopy() { - saxParser = createSAXParser(); - } - - public void validate(String text) { - StringBuilder markup = new StringBuilder(); - markup.append(DTD); - markup.append(""); - markup.append(text); - markup.append(""); - InputSource inputSource = new InputSource(new StringReader( - markup.toString())); - try { - saxParser.parse(inputSource, new MarkupHandler()); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalArgumentException("Failed to parse markup text", - exception); - } - } - - public static boolean isValidationDisabledFor(Widget widget) { - return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED)); - } - - private static SAXParser createSAXParser() { - SAXParser result = null; - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - try { - result = parserFactory.newSAXParser(); - } catch (Exception exception) { - throw new RuntimeException("Failed to create SAX parser", exception); - } - return result; - } - - private static String createDTD() { - StringBuilder result = new StringBuilder(); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append("]>"); - return result.toString(); - } - - private static Map createSupportedElementsMap() { - Map result = new HashMap(); - result.put("html", new String[0]); - result.put("br", new String[0]); - result.put("b", new String[] { "style" }); - result.put("strong", new String[] { "style" }); - result.put("i", new String[] { "style" }); - result.put("em", new String[] { "style" }); - result.put("sub", new String[] { "style" }); - result.put("sup", new String[] { "style" }); - result.put("big", new String[] { "style" }); - result.put("small", new String[] { "style" }); - result.put("del", new String[] { "style" }); - result.put("ins", new String[] { "style" }); - result.put("code", new String[] { "style" }); - result.put("samp", new String[] { "style" }); - result.put("kbd", new String[] { "style" }); - result.put("var", new String[] { "style" }); - result.put("cite", new String[] { "style" }); - result.put("dfn", new String[] { "style" }); - result.put("q", new String[] { "style" }); - result.put("abbr", new String[] { "style", "title" }); - result.put("span", new String[] { "style" }); - result.put("img", new String[] { "style", "src", "width", "height", - "title", "alt" }); - result.put("a", new String[] { "style", "href", "target", "title" }); - return result; - } - - private static class MarkupHandler extends DefaultHandler { - - @Override - public void startElement(String uri, String localName, String name, - Attributes attributes) { - checkSupportedElements(name, attributes); - checkSupportedAttributes(name, attributes); - checkMandatoryAttributes(name, attributes); - } - - private static void checkSupportedElements(String elementName, - Attributes attributes) { - if (!SUPPORTED_ELEMENTS.containsKey(elementName)) { - throw new IllegalArgumentException( - "Unsupported element in markup text: " + elementName); - } - } - - private static void checkSupportedAttributes(String elementName, - Attributes attributes) { - if (attributes.getLength() > 0) { - List supportedAttributes = Arrays - .asList(SUPPORTED_ELEMENTS.get(elementName)); - int index = 0; - String attributeName = attributes.getQName(index); - while (attributeName != null) { - if (!supportedAttributes.contains(attributeName)) { - String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; - message = MessageFormat.format(message, new Object[] { - attributeName, elementName }); - throw new IllegalArgumentException(message); - } - index++; - attributeName = attributes.getQName(index); - } - } - } - - private static void checkMandatoryAttributes(String elementName, - Attributes attributes) { - checkIntAttribute(elementName, attributes, "img", "width"); - checkIntAttribute(elementName, attributes, "img", "height"); - } - - private static void checkIntAttribute(String elementName, - Attributes attributes, String checkedElementName, - String checkedAttributeName) { - if (checkedElementName.equals(elementName)) { - String attribute = attributes.getValue(checkedAttributeName); - try { - Integer.parseInt(attribute); - } catch (NumberFormatException exception) { - String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; - Object[] arguments = new Object[] { checkedAttributeName, - checkedElementName }; - message = MessageFormat.format(message, arguments); - throw new IllegalArgumentException(message); - } - } - } - - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/text/SectionTitle.java b/org.argeo.cms/src/org/argeo/cms/internal/text/SectionTitle.java deleted file mode 100644 index 160060ea3..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/text/SectionTitle.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.cms.internal.text; - -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -import org.argeo.cms.text.TextSection; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.viewers.PropertyPart; -import org.argeo.cms.widgets.EditableText; -import org.eclipse.swt.widgets.Composite; - -/** The title of a section. */ -public class SectionTitle extends EditableText implements EditablePart, - PropertyPart { - private static final long serialVersionUID = -1787983154946583171L; - - private final TextSection section; - - public SectionTitle(Composite parent, int swtStyle, Property title) - throws RepositoryException { - super(parent, swtStyle, title); - section = (TextSection) TextSection.findSection(this); - } - - public TextSection getSection() { - return section; - } - - // @Override - // public Property getProperty() throws RepositoryException { - // return getSection().getNode().getProperty(Property.JCR_TITLE); - // } - - @Override - public Property getItem() throws RepositoryException { - return getProperty(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/text/TextContextMenu.java b/org.argeo.cms/src/org/argeo/cms/internal/text/TextContextMenu.java deleted file mode 100644 index 8afad1629..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/text/TextContextMenu.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.argeo.cms.internal.text; - -import java.util.ArrayList; -import java.util.List; - -import org.argeo.cms.CmsNames; -import org.argeo.cms.text.Paragraph; -import org.argeo.cms.text.TextStyles; -import org.argeo.cms.viewers.EditablePart; -import org.argeo.cms.viewers.SectionPart; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Dialog to edit a text part. */ -class TextContextMenu extends Shell implements CmsNames, TextStyles { - private final static String[] DEFAULT_TEXT_STYLES = { - TextStyles.TEXT_DEFAULT, TextStyles.TEXT_PRE, TextStyles.TEXT_QUOTE }; - - private final AbstractTextViewer textViewer; - - private static final long serialVersionUID = -3826246895162050331L; - private List styleButtons = new ArrayList(); - - private Label deleteButton, publishButton, editButton; - - private EditablePart currentTextPart; - - public TextContextMenu(AbstractTextViewer textViewer, Display display) { - super(display, SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - this.textViewer = textViewer; - setLayout(new GridLayout()); - setData(RWT.CUSTOM_VARIANT, TEXT_STYLED_TOOLS_DIALOG); - - StyledToolMouseListener stml = new StyledToolMouseListener(); - if (textViewer.getCmsEditable().isEditing()) { - for (String style : DEFAULT_TEXT_STYLES) { - StyleButton styleButton = new StyleButton(this, SWT.WRAP); - styleButton.setData(RWT.CUSTOM_VARIANT, style); - styleButton.setData(RWT.MARKUP_ENABLED, true); - styleButton.addMouseListener(stml); - styleButtons.add(styleButton); - } - - // Delete - deleteButton = new Label(this, SWT.NONE); - deleteButton.setText("Delete"); - deleteButton.addMouseListener(stml); - - // Publish - publishButton = new Label(this, SWT.NONE); - publishButton.setText("Publish"); - publishButton.addMouseListener(stml); - } else if (textViewer.getCmsEditable().canEdit()) { - // Edit - editButton = new Label(this, SWT.NONE); - editButton.setText("Edit"); - editButton.addMouseListener(stml); - } - addShellListener(new ToolsShellListener()); - } - - public void show(EditablePart source, Point location) { - if (isVisible()) - setVisible(false); - - this.currentTextPart = source; - - if (currentTextPart instanceof Paragraph) { - final int size = 32; - String text = textViewer - .getRawParagraphText((Paragraph) currentTextPart); - String textToShow = text.length() > size ? text.substring(0, - size - 3) + "..." : text; - for (StyleButton styleButton : styleButtons) { - styleButton.setText(textToShow); - } - } - pack(); - layout(); - if (source instanceof Control) - setLocation(((Control) source).toDisplay(location.x, location.y)); - open(); - } - - class StyleButton extends Label { - private static final long serialVersionUID = 7731102609123946115L; - - public StyleButton(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - } - - class StyledToolMouseListener extends MouseAdapter { - private static final long serialVersionUID = 8516297091549329043L; - - @Override - public void mouseDown(MouseEvent e) { - Object eventSource = e.getSource(); - if (eventSource instanceof StyleButton) { - StyleButton sb = (StyleButton) e.getSource(); - String style = sb.getData(RWT.CUSTOM_VARIANT).toString(); - textViewer - .setParagraphStyle((Paragraph) currentTextPart, style); - } else if (eventSource == deleteButton) { - textViewer.deletePart((SectionPart) currentTextPart); - } else if (eventSource == editButton) { - textViewer.getCmsEditable().startEditing(); - } else if (eventSource == publishButton) { - textViewer.getCmsEditable().stopEditing(); - } - setVisible(false); - } - } - - class ToolsShellListener extends org.eclipse.swt.events.ShellAdapter { - private static final long serialVersionUID = 8432350564023247241L; - - @Override - public void shellDeactivated(ShellEvent e) { - setVisible(false); - } - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/text/TextInterpreterImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/text/TextInterpreterImpl.java deleted file mode 100644 index c252efe4f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/text/TextInterpreterImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.internal.text; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.cms.text.IdentityTextInterpreter; - -/** - * Text interpreter that sanitise and validates before saving, and support CMS - * specific formatting and integration. - */ -class TextInterpreterImpl extends IdentityTextInterpreter { - private MarkupValidatorCopy markupValidator = MarkupValidatorCopy - .getInstance(); - - @Override - protected void validateBeforeStoring(String raw) { - markupValidator.validate(raw); - } - - @Override - protected String convertToStorage(Item item, String content) - throws RepositoryException { - return super.convertToStorage(item, content); - } - - @Override - protected String convertFromStorage(Item item, String content) - throws RepositoryException { - return super.convertFromStorage(item, content); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java b/org.argeo.cms/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java deleted file mode 100644 index 2494d90c3..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/AbstractOsgiComposite.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.util.Collection; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -abstract class AbstractOsgiComposite extends Composite { - private static final long serialVersionUID = -4097415973477517137L; - protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - protected final Log log = LogFactory.getLog(getClass()); - - public AbstractOsgiComposite(Composite parent, int style) { - super(parent, style); - parent.setLayout(CmsUtils.noSpaceGridLayout()); - setLayout(CmsUtils.noSpaceGridLayout()); - setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - initUi(style); - } - - protected abstract void initUi(int style); - - protected T getService(Class clazz) { - return bc.getService(bc.getServiceReference(clazz)); - } - - protected Collection> getServiceReferences(Class clazz, String filter) { - try { - return bc.getServiceReferences(clazz, filter); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Filter " + filter + " is invalid", e); - } - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/Browse.java b/org.argeo.cms/src/org/argeo/cms/maintenance/Browse.java deleted file mode 100644 index 7dafd72c4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/Browse.java +++ /dev/null @@ -1,616 +0,0 @@ -package org.argeo.cms.maintenance; - -import static javax.jcr.Node.JCR_CONTENT; -import static org.eclipse.swt.SWT.RIGHT; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.LinkedHashMap; - -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsTypes; -import org.argeo.cms.CmsUiProvider; -import org.argeo.cms.text.Img; -import org.argeo.cms.util.CmsLink; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.widgets.EditableImage; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ILazyContentProvider; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.Text; - -public class Browse implements CmsUiProvider { - - // Some local constants to experiment. should be cleaned - private final static String BROWSE_PREFIX = "browse#"; - private final static int THUMBNAIL_WIDTH = 400; - private final static int COLUMN_WIDTH = 160; - private DateFormat timeFormatter = new SimpleDateFormat( - "dd-MM-yyyy', 'HH:mm"); - - // keep a cache of the opened nodes - // Key is the path - private LinkedHashMap browserCols = new LinkedHashMap(); - private Composite nodeDisplayParent; - private Composite colViewer; - private ScrolledComposite scrolledCmp; - private Text parentPathTxt; - private Text filterTxt; - private Node currEdited; - - private String initialPath; - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - if (context == null) - // return null; - throw new CmsException("Context cannot be null"); - GridLayout layout = CmsUtils.noSpaceGridLayout(); - layout.numColumns = 2; - parent.setLayout(layout); - - // Left - Composite leftCmp = new Composite(parent, SWT.NO_FOCUS); - leftCmp.setLayoutData(CmsUtils.fillAll()); - createBrowserPart(leftCmp, context); - - // Right - nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); - GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true); - gd.widthHint = THUMBNAIL_WIDTH; - nodeDisplayParent.setLayoutData(gd); - createNodeView(nodeDisplayParent, context); - - // INIT - setEdited(context); - initialPath = context.getPath(); - - // Workaround we don't yet manage the delete to display parent of the - // initial context node - - return null; - } - - private void createBrowserPart(Composite parent, Node context) - throws RepositoryException { - GridLayout layout = CmsUtils.noSpaceGridLayout(); - parent.setLayout(layout); - Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); - filterCmp.setLayoutData(CmsUtils.fillWidth()); - - // top filter - addFilterPanel(filterCmp); - - // scrolled composite - scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER - | SWT.NO_FOCUS); - scrolledCmp.setLayoutData(CmsUtils.fillAll()); - scrolledCmp.setExpandVertical(true); - scrolledCmp.setExpandHorizontal(true); - scrolledCmp.setShowFocusedControl(true); - - colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS); - scrolledCmp.setContent(colViewer); - scrolledCmp.addControlListener(new ControlAdapter() { - private static final long serialVersionUID = 6589392045145698201L; - - @Override - public void controlResized(ControlEvent e) { - Rectangle r = scrolledCmp.getClientArea(); - scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, - r.height)); - } - }); - initExplorer(colViewer, context); - } - - private Control initExplorer(Composite parent, Node context) - throws RepositoryException { - parent.setLayout(CmsUtils.noSpaceGridLayout()); - createBrowserColumn(parent, context); - return null; - } - - private Control createBrowserColumn(Composite parent, Node context) - throws RepositoryException { - // TODO style is not correctly managed. - FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable( - parent, SWT.BORDER | SWT.NO_FOCUS, context); - // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style()); - table.filterList("*"); - table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); - browserCols.put(context.getPath(), table); - return null; - } - - public void addFilterPanel(Composite parent) { - - parent.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(2, false))); - - // Text Area for the filter - parentPathTxt = new Text(parent, SWT.NO_FOCUS); - parentPathTxt.setEditable(false); - filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); - filterTxt.setMessage("Filter current list"); - filterTxt.setLayoutData(CmsUtils.fillWidth()); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7709303319740056286L; - - public void modifyText(ModifyEvent event) { - modifyFilter(false); - } - }); - - filterTxt.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -4523394262771183968L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0; - // boolean altPressed = (e.stateMask & SWT.ALT) != 0; - FilterEntitiesVirtualTable currTable = null; - if (currEdited != null) { - FilterEntitiesVirtualTable table = browserCols - .get(getPath(currEdited)); - if (table != null && !table.isDisposed()) - currTable = table; - } - - try { - if (e.keyCode == SWT.ARROW_DOWN) - currTable.setFocus(); - else if (e.keyCode == SWT.BS) { - if (filterTxt.getText().equals("") - && !(getPath(currEdited).equals("/") || getPath( - currEdited).equals(initialPath))) { - setEdited(currEdited.getParent()); - e.doit = false; - filterTxt.setFocus(); - } - } else if (e.keyCode == SWT.TAB && !shiftPressed) { - if (currEdited.getNodes(filterTxt.getText() + "*") - .getSize() == 1) { - setEdited(currEdited.getNodes( - filterTxt.getText() + "*").nextNode()); - } - filterTxt.setFocus(); - e.doit = false; - } - } catch (RepositoryException e1) { - throw new CmsException( - "Unexpected error in key management for " - + currEdited + "with filter " - + filterTxt.getText(), e1); - } - - } - }); - } - - private void setEdited(Node node) { - try { - currEdited = node; - CmsUtils.clear(nodeDisplayParent); - createNodeView(nodeDisplayParent, currEdited); - nodeDisplayParent.layout(); - refreshFilters(node); - refreshBrowser(node); - } catch (RepositoryException re) { - throw new CmsException("Unable to update browser for " + node, re); - } - } - - private void refreshFilters(Node node) throws RepositoryException { - String currNodePath = node.getPath(); - parentPathTxt.setText(currNodePath); - filterTxt.setText(""); - filterTxt.getParent().layout(); - } - - private void refreshBrowser(Node node) throws RepositoryException { - - // Retrieve - String currNodePath = node.getPath(); - String currParPath = ""; - if (!"/".equals(currNodePath)) - currParPath = JcrUtils.parentPath(currNodePath); - if ("".equals(currParPath)) - currParPath = "/"; - - - - - Object[][] colMatrix = new Object[browserCols.size()][2]; - - int i = 0, j = -1, k = -1; - for (String path : browserCols.keySet()) { - colMatrix[i][0] = path; - colMatrix[i][1] = browserCols.get(path); - if (j >= 0 && k < 0 && !currNodePath.equals("/")) { - boolean leaveOpened = path.startsWith(currNodePath); - - // workaround for same name siblings - // fix me weird side effect when we go left or click on anb - // already selected, unfocused node - if (leaveOpened - && (path.lastIndexOf("/") == 0 - && currNodePath.lastIndexOf("/") == 0 || JcrUtils - .parentPath(path).equals( - JcrUtils.parentPath(currNodePath)))) - leaveOpened = JcrUtils.lastPathElement(path).equals( - JcrUtils.lastPathElement(currNodePath)); - - if (!leaveOpened) - k = i; - } - if (currParPath.equals(path)) - j = i; - i++; - } - - if (j >= 0 && k >= 0) - // remove useless cols - for (int l = i - 1; l >= k; l--) { - browserCols.remove(colMatrix[l][0]); - ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); - } - - // Remove disposed columns - // TODO investigate and fix the mechanism that leave them there after - // disposal - if (browserCols.containsKey(currNodePath)) { - FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath); - if (currCol.isDisposed()) - browserCols.remove(currNodePath); - } - - if (!browserCols.containsKey(currNodePath)) - createBrowserColumn(colViewer, node); - - colViewer.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout( - browserCols.size(), false))); - // colViewer.pack(); - colViewer.layout(); - // also resize the scrolled composite - scrolledCmp.layout(); - scrolledCmp.getShowFocusedControl(); - // colViewer.getParent().layout(); - // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) { - // } else { - // } - } - - private void modifyFilter(boolean fromOutside) { - if (!fromOutside) - if (currEdited != null) { - String filter = filterTxt.getText() + "*"; - FilterEntitiesVirtualTable table = browserCols - .get(getPath(currEdited)); - if (table != null && !table.isDisposed()) - table.filterList(filter); - } - - } - - private String getPath(Node node) { - try { - return node.getPath(); - } catch (RepositoryException e) { - throw new CmsException("Unable to get path for node " + node, e); - } - } - - private Point imageWidth = new Point(250, 0); - - /** - * Recreates the content of the box that displays information about the - * current selected node. - */ - private Control createNodeView(Composite parent, Node context) - throws RepositoryException { - - parent.setLayout(new GridLayout(2, false)); - - if (isImg(context)) { - EditableImage image = new Img(parent, RIGHT, context, imageWidth); - image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, - false, 2, 1)); - } - - // Name and primary type - Label contextL = new Label(parent, SWT.NONE); - contextL.setData(RWT.MARKUP_ENABLED, true); - contextL.setText("" + context.getName() + ""); - new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() - .getName()); - - // Children - for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { - Node child = nIt.nextNode(); - new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()) - .createUi(parent, context); - new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() - .getName()); - } - - // Properties - for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { - Property property = pIt.nextProperty(); - Label label = new Label(parent, SWT.NONE); - label.setText(property.getName()); - label.setToolTipText(JcrUtils - .getPropertyDefinitionAsString(property)); - new Label(parent, SWT.NONE).setText(getPropAsString(property)); - } - - return null; - } - - private boolean isImg(Node node) throws RepositoryException { - return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE); - } - - private String getPropAsString(Property property) - throws RepositoryException { - String result = ""; - if (property.isMultiple()) { - result = getMultiAsString(property, ", "); - } else { - Value value = property.getValue(); - if (value.getType() == PropertyType.BINARY) - result = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) - throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } - - /** Almost canonical implementation of a table that display entities */ - private class FilterEntitiesVirtualTable extends Composite { - private static final long serialVersionUID = 8798147431706283824L; - - // Context - private Node context; - - // UI Objects - private TableViewer entityViewer; - - // enable management of multiple columns - Node getNode() { - return context; - } - - @Override - public boolean setFocus() { - if (entityViewer.getTable().isDisposed()) - return false; - if (entityViewer.getSelection().isEmpty()) { - Object first = entityViewer.getElementAt(0); - if (first != null) { - entityViewer.setSelection(new StructuredSelection(first), - true); - } - } - return entityViewer.getTable().setFocus(); - } - - void filterList(String filter) { - try { - NodeIterator nit = context.getNodes(filter); - refreshFilteredList(nit); - } catch (RepositoryException e) { - throw new CmsException("Unable to filter " + getNode() - + " children with filter " + filter, e); - } - - } - - public FilterEntitiesVirtualTable(Composite parent, int style, - Node context) { - super(parent, SWT.NO_FOCUS); - this.context = context; - populate(); - } - - protected void populate() { - Composite parent = this; - GridLayout layout = CmsUtils.noSpaceGridLayout(); - - this.setLayout(layout); - createTableViewer(parent); - } - - private void createTableViewer(final Composite parent) { - // the list - // We must limit the size of the table otherwise the full list is - // loaded - // before the layout happens - Composite listCmp = new Composite(parent, SWT.NO_FOCUS); - GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); - gd.widthHint = COLUMN_WIDTH; - listCmp.setLayoutData(gd); - listCmp.setLayout(CmsUtils.noSpaceGridLayout()); - - entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); - Table table = entityViewer.getTable(); - - table.setLayoutData(CmsUtils.fillAll()); - table.setLinesVisible(true); - table.setHeaderVisible(false); - table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); - - CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - - // first column - TableViewerColumn column = new TableViewerColumn(entityViewer, - SWT.NONE); - TableColumn tcol = column.getColumn(); - tcol.setWidth(COLUMN_WIDTH); - tcol.setResizable(true); - column.setLabelProvider(new SimpleNameLP()); - - entityViewer.setContentProvider(new MyLazyCP(entityViewer)); - entityViewer - .addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) entityViewer - .getSelection(); - if (selection.isEmpty()) - return; - else - setEdited((Node) selection.getFirstElement()); - - } - }); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -330694313896036230L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - - IStructuredSelection selection = (IStructuredSelection) entityViewer - .getSelection(); - Node selected = null; - if (!selection.isEmpty()) - selected = ((Node) selection.getFirstElement()); - try { - if (e.keyCode == SWT.ARROW_RIGHT) { - if (selected != null) { - setEdited(selected); - browserCols.get(selected.getPath()).setFocus(); - } - } else if (e.keyCode == SWT.ARROW_LEFT) { - try { - selected = getNode().getParent(); - String newPath = selected.getPath(); // getNode().getParent() - setEdited(selected); - if (browserCols.containsKey(newPath)) - browserCols.get(newPath).setFocus(); - } catch (ItemNotFoundException ie) { - // root silent - } - } - } catch (RepositoryException ie) { - throw new CmsException("Error while managing arrow " - + "events in the browser for " + selected, ie); - } - } - }); - } - - private class MyLazyCP implements ILazyContentProvider { - private static final long serialVersionUID = 1L; - private TableViewer viewer; - private Object[] elements; - - public MyLazyCP(TableViewer viewer) { - this.viewer = viewer; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, - Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - public void updateElement(int index) { - viewer.replace(elements[index], index); - } - } - - protected void refreshFilteredList(NodeIterator children) { - Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); - entityViewer.setInput(rows); - entityViewer.setItemCount(rows.length); - entityViewer.refresh(); - } - - public class SimpleNameLP extends ColumnLabelProvider { - private static final long serialVersionUID = 2465059387875338553L; - - @Override - public String getText(Object element) { - if (element instanceof Node) { - Node curr = ((Node) element); - try { - return curr.getName(); - } catch (RepositoryException e) { - throw new CmsException("Unable to get name for" - + curr); - } - } - return super.getText(element); - } - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java b/org.argeo.cms/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java deleted file mode 100644 index f4f3079b6..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/ConnectivityDeploymentUi.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.cms.maintenance; - -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; -import org.osgi.service.useradmin.UserAdmin; - -class ConnectivityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public ConnectivityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - StringBuffer text = new StringBuffer(); - text.append("Provided Servers
"); - - ServiceReference userAdminRef = bc.getServiceReference(HttpService.class); - if (userAdminRef != null) { - // FIXME use constants - Object httpPort = userAdminRef.getProperty("http.port"); - Object httpsPort = userAdminRef.getProperty("https.port"); - if (httpPort != null) - text.append("http ").append(httpPort).append("
"); - if (httpsPort != null) - text.append("https ").append(httpsPort).append("
"); - - } - - text.append("
"); - text.append("Referenced Servers
"); - - Label label = new Label(this, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/DataDeploymentUi.java b/org.argeo.cms/src/org/argeo/cms/maintenance/DataDeploymentUi.java deleted file mode 100644 index 49f3fc351..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/DataDeploymentUi.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.argeo.cms.CmsException; -import org.argeo.cms.internal.kernel.JackrabbitType; -import org.argeo.cms.util.CmsUtils; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.node.NodeConstants; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.ServiceReference; -import org.osgi.service.cm.Configuration; -import org.osgi.service.cm.ConfigurationAdmin; - -class DataDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public DataDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { - try { - ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); - Configuration[] confs = confAdmin.listConfigurations( - "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); - if (confs == null || confs.length == 0) { - Group buttonGroup = new Group(parent, SWT.NONE); - buttonGroup.setText("Repository Type"); - buttonGroup.setLayout(new GridLayout(2, true)); - buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); - - SelectionListener selectionListener = new SelectionAdapter() { - private static final long serialVersionUID = 6247064348421088092L; - - public void widgetSelected(SelectionEvent event) { - Button radio = (Button) event.widget; - if (!radio.getSelection()) - return; - log.debug(event); - JackrabbitType nodeType = (JackrabbitType) radio.getData(); - if (log.isDebugEnabled()) - log.debug(" selected = " + nodeType.name()); - }; - }; - - for (JackrabbitType nodeType : JackrabbitType.values()) { - Button radio = new Button(buttonGroup, SWT.RADIO); - radio.setText(nodeType.name()); - radio.setData(nodeType); - if (nodeType.equals(JackrabbitType.localfs)) - radio.setSelection(true); - radio.addSelectionListener(selectionListener); - } - - } else if (confs.length == 1) { - - } else { - throw new CmsException("Multiple repos not yet supported"); - } - } catch (Exception e) { - throw new CmsException("Cannot initialize UI", e); - } - - } - - private void initCurrentUi(Composite parent) { - parent.setLayout(new GridLayout()); - Collection> contexts = getServiceReferences(RepositoryContext.class, - "(" + ArgeoJcrConstants.JCR_REPOSITORY_ALIAS + "=*)"); - StringBuffer text = new StringBuffer(); - text.append("Jackrabbit Repositories
"); - for (ServiceReference sr : contexts) { - RepositoryContext repositoryContext = bc.getService(sr); - String alias = sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS).toString(); - String rootNodeId = repositoryContext.getRootNodeId().toString(); - RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig(); - Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath(); - // TODO check data store - - text.append("" + alias + "
"); - text.append("rootNodeId: " + rootNodeId + "
"); - try { - FileStore fileStore = Files.getFileStore(repoHomePath); - text.append("partition: " + fileStore.toString() + "
"); - text.append( - percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); - } catch (IOException e) { - log.error("Cannot check fileStore for " + repoHomePath, e); - } - } - Label label = new Label(parent, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsUtils.markup(label); - label.setText("" + text.toString() + ""); - } - - private String humanReadable(long bytes) { - long mb = bytes / (1024 * 1024); - return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB"; - } - - private String percentUsed(FileStore fs) throws IOException { - long used = fs.getTotalSpace() - fs.getUnallocatedSpace(); - long percent = used * 100 / fs.getTotalSpace(); - if (log.isTraceEnabled()) { - // output identical to `df -B 1`) - log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace()); - } - String span; - if (percent < 80) - span = ""; - else if (percent < 95) - span = ""; - else - span = ""; - return span + percent + "%"; - } - - protected boolean isDeployed() { - return bc.getServiceReference(RepositoryContext.class) != null; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/DataMigration.java b/org.argeo.cms/src/org/argeo/cms/maintenance/DataMigration.java deleted file mode 100644 index 3aa61207f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/DataMigration.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.util.Map; - -import javax.jcr.Session; - -public interface DataMigration { - /** Migrate data between two workspaces, at JCR level. */ - Boolean migrate(Session source, Session target); - - /** - * Keys are the source workspaces and values the target workspaces. If null - * is returned, only the default workspace will be migrated, to the default - * workspace of the target repository. - */ - Map workspacesToMigrate(); - -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java b/org.argeo.cms/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java deleted file mode 100644 index e21974c35..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/DeploymentEntryPoint.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.cms.util.CmsUtils; -import org.argeo.node.NodeConstants; -import org.argeo.node.NodeDeployment; -import org.argeo.node.NodeState; -import org.eclipse.rap.rwt.application.AbstractEntryPoint; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; - -class DeploymentEntryPoint extends AbstractEntryPoint { - private static final long serialVersionUID = -881152502968982437L; - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - @Override - protected void createContents(Composite parent) { - // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - if (isDesktop()) { - parent.setLayout(new GridLayout(2, true)); - } else { - // TODO add scrolling - parent.setLayout(new GridLayout(1, true)); - } - - initHighLevelSummary(parent); - - Group securityGroup = createHighLevelGroup(parent, "Security"); - securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new SecurityDeploymentUi(securityGroup, SWT.NONE); - - Group dataGroup = createHighLevelGroup(parent, "Data"); - dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new DataDeploymentUi(dataGroup, SWT.NONE); - - Group logGroup = createHighLevelGroup(parent, "Notifications"); - logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - new LogDeploymentUi(logGroup, SWT.NONE); - - Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); - new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); - connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - - } - - private void initHighLevelSummary(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); - if (isDesktop()) - gridData.horizontalSpan = 3; - composite.setLayoutData(gridData); - composite.setLayout(new FillLayout()); - - ServiceReference nodeStateRef = bc.getServiceReference(NodeState.class); - if (nodeStateRef == null) - throw new IllegalStateException("No CMS state available"); - NodeState nodeState = bc.getService(nodeStateRef); - ServiceReference nodeDeploymentRef = bc.getServiceReference(NodeDeployment.class); - Label label = new Label(composite, SWT.WRAP); - CmsUtils.markup(label); - if (nodeDeploymentRef == null) { - label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, please configure below."); - } else { - Object stateUuid = nodeStateRef.getProperty(NodeConstants.CN); - NodeDeployment nodeDeployment = bc.getService(nodeDeploymentRef); - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(nodeDeployment.getAvailableSince()); - calendar.setTimeZone(TimeZone.getDefault()); - label.setText("[" + "" + nodeState.getHostname() + "]# " + "Deployment state " + stateUuid - + ", available since " + calendar.getTime() + ""); - } - } - - private static Group createHighLevelGroup(Composite parent, String text) { - Group group = new Group(parent, SWT.NONE); - group.setText(text); - CmsUtils.markup(group); - return group; - } - - private boolean isDesktop() { - return true; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/LogDeploymentUi.java b/org.argeo.cms/src/org/argeo/cms/maintenance/LogDeploymentUi.java deleted file mode 100644 index 8fb96437c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/LogDeploymentUi.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Enumeration; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.LogReaderService; - -class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { - private static final long serialVersionUID = 590221539553514693L; - - private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); - - private Display display; - private Text logDisplay; - - public LogDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - LogReaderService logReader = getService(LogReaderService.class); - // FIXME use server push - // logReader.addLogListener(this); - this.display = getDisplay(); - this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); - logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - CmsUtils.markup(logDisplay); - @SuppressWarnings("unchecked") - Enumeration logEntries = (Enumeration) logReader.getLog(); - while (logEntries.hasMoreElements()) - logDisplay.append(printEntry(logEntries.nextElement())); - } - - private String printEntry(LogEntry entry) { - StringBuilder sb = new StringBuilder(); - GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); - calendar.setTimeInMillis(entry.getTime()); - sb.append(dateFormat.format(calendar.getTime())).append(' '); - sb.append(entry.getMessage()); - sb.append('\n'); - return sb.toString(); - } - - @Override - public void logged(LogEntry entry) { - if (display.isDisposed()) - return; - display.asyncExec(() -> { - if (logDisplay.isDisposed()) - return; - logDisplay.append(printEntry(entry)); - }); - display.wake(); - } - - // @Override - // public void dispose() { - // super.dispose(); - // getService(LogReaderService.class).removeLogListener(this); - // } -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceStyles.java b/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceStyles.java deleted file mode 100644 index fef25d7ce..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceStyles.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.maintenance; - -/** Specific styles used by the various maintenance pages . */ -public interface MaintenanceStyles { - // General - public final static String PREFIX = "maintenance_"; - - // Browser - public final static String BROWSER_COLUMN = "browser_column"; - } diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceUi.java b/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceUi.java deleted file mode 100644 index 538379f06..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/MaintenanceUi.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.maintenance; - -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; - -public class MaintenanceUi implements ApplicationConfiguration { - - @Override - public void configure(Application application) { - application.addEntryPoint("/status", DeploymentEntryPoint.class, null); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/NonAdminPage.java b/org.argeo.cms/src/org/argeo/cms/maintenance/NonAdminPage.java deleted file mode 100644 index b27d50659..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/NonAdminPage.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.cms.maintenance; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsUiProvider; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class NonAdminPage implements CmsUiProvider{ - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Composite body = new Composite(parent, SWT.NO_FOCUS); - body.setLayoutData(CmsUtils.fillAll()); - body.setLayout(new GridLayout()); - Label label = new Label(body, SWT.NONE); - label.setText("You should be an admin to perform maintenance operations. " - + "Are you sure you are logged in?"); - label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); - return null; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java b/org.argeo.cms/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java deleted file mode 100644 index 9fcdaf980..000000000 --- a/org.argeo.cms/src/org/argeo/cms/maintenance/SecurityDeploymentUi.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.cms.maintenance; - -import java.net.URI; - -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -class SecurityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public SecurityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { - new Label(parent, SWT.NONE).setText("Security is not configured"); - } - - private void initCurrentUi(Composite parent) { - ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); - UserAdmin userAdmin = bc.getService(userAdminRef); - StringBuffer text = new StringBuffer(); - text.append("Domains
"); - domains: for (String key : userAdminRef.getPropertyKeys()) { - if (!key.startsWith("/")) - continue domains; - URI uri; - try { - uri = new URI(key); - } catch (Exception e) { - // ignore non URI keys - continue domains; - } - - String rootDn = uri.getPath().substring(1, uri.getPath().length()); - // FIXME make reading query options more robust, using utils - boolean readOnly = uri.getQuery().equals("readOnly=true"); - if (readOnly) - text.append(""); - else - text.append(""); - - text.append(rootDn); - text.append("
"); - try { - Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")"); - long userCount = 0; - long groupCount = 0; - for (Role role : roles) { - if (role.getType() == Role.USER) - userCount++; - else - groupCount++; - } - text.append(" " + userCount + " users, " + groupCount +" groups.
"); - } catch (InvalidSyntaxException e) { - log.error("Invalid syntax", e); - } - } - Label label = new Label(parent, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/CustomTextEditor.java b/org.argeo.cms/src/org/argeo/cms/text/CustomTextEditor.java deleted file mode 100644 index 900dce2ae..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/CustomTextEditor.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.cms.text; - -import static org.argeo.cms.util.CmsUtils.fillWidth; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.internal.text.AbstractTextViewer; -import org.argeo.cms.viewers.Section; -import org.eclipse.swt.widgets.Composite; - -/** - * Manages hardcoded sections as an arbitrary hierarchy under the main section, - * which contains no text and no title. - */ -public class CustomTextEditor extends AbstractTextViewer { - private static final long serialVersionUID = 5277789504209413500L; - - public CustomTextEditor(Composite parent, int style, Node textNode, - CmsEditable cmsEditable) throws RepositoryException { - this(new Section(parent, style, textNode), style, cmsEditable); - } - - public CustomTextEditor(Section mainSection, int style, - CmsEditable cmsEditable) throws RepositoryException { - super(mainSection, style, cmsEditable); - mainSection.setLayoutData(fillWidth()); - } - - @Override - public Section getMainSection() { - return super.getMainSection(); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/IdentityTextInterpreter.java b/org.argeo.cms/src/org/argeo/cms/text/IdentityTextInterpreter.java deleted file mode 100644 index 79f6ede2c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/IdentityTextInterpreter.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsNames; -import org.argeo.cms.CmsTypes; - -/** Based on HTML with a few Wiki-like shortcuts. */ -public class IdentityTextInterpreter implements TextInterpreter, CmsNames { - - @Override - public void write(Item item, String content) { - try { - if (item instanceof Node) { - Node node = (Node) item; - if (node.isNodeType(CmsTypes.CMS_STYLED)) { - String raw = convertToStorage(node, content); - validateBeforeStoring(raw); - node.setProperty(CMS_CONTENT, raw); - } else { - throw new CmsException("Don't know how to interpret " - + node); - } - } else {// property - Property property = (Property) item; - property.setValue(content); - } - // item.getSession().save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot set content on " + item, e); - } - } - - @Override - public String read(Item item) { - try { - String raw = raw(item); - return convertFromStorage(item, raw); - } catch (RepositoryException e) { - throw new CmsException("Cannot get " + item + " for edit", e); - } - } - - @Override - public String raw(Item item) { - try { - item.getSession().refresh(true); - if (item instanceof Node) { - Node node = (Node) item; - if (node.isNodeType(CmsTypes.CMS_STYLED)) { - // WORKAROUND FOR BROKEN PARARAPHS - if (!node.hasProperty(CMS_CONTENT)) { - node.setProperty(CMS_CONTENT, ""); - node.getSession().save(); - } - - return node.getProperty(CMS_CONTENT).getString(); - } else { - throw new CmsException("Don't know how to interpret " - + node); - } - } else {// property - Property property = (Property) item; - return property.getString(); - } - } catch (RepositoryException e) { - throw new CmsException("Cannot get " + item + " content", e); - } - } - - // EXTENSIBILITY - /** - * To be overridden, in order to make sure that only valid strings are being - * stored. - */ - protected void validateBeforeStoring(String raw) { - } - - /** To be overridden, in order to support additional formatting. */ - protected String convertToStorage(Item item, String content) - throws RepositoryException { - return content; - - } - - /** To be overridden, in order to support additional formatting. */ - protected String convertFromStorage(Item item, String content) - throws RepositoryException { - return content; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/Img.java b/org.argeo.cms/src/org/argeo/cms/text/Img.java deleted file mode 100644 index 8c23109bf..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/Img.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.internal.JcrFileUploadReceiver; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.NodePart; -import org.argeo.cms.viewers.Section; -import org.argeo.cms.viewers.SectionPart; -import org.argeo.cms.widgets.EditableImage; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** An image within the Argeo Text framework */ -public class Img extends EditableImage implements SectionPart, NodePart { - private static final long serialVersionUID = 6233572783968188476L; - - private final Section section; - - private final CmsImageManager imageManager; - private FileUploadHandler currentUploadHandler = null; - private FileUploadListener fileUploadListener; - - public Img(Composite parent, int swtStyle, Node imgNode, - Point preferredImageSize) throws RepositoryException { - this(Section.findSection(parent), parent, swtStyle, imgNode, - preferredImageSize); - setStyle(TextStyles.TEXT_IMAGE); - } - - public Img(Composite parent, int swtStyle, Node imgNode) - throws RepositoryException { - this(Section.findSection(parent), parent, swtStyle, imgNode, null); - setStyle(TextStyles.TEXT_IMAGE); - } - - Img(Section section, Composite parent, int swtStyle, Node imgNode, - Point preferredImageSize) throws RepositoryException { - super(parent, swtStyle, imgNode, false, preferredImageSize); - this.section = section; - imageManager = CmsUtils.getCmsView().getImageManager(); - CmsUtils.style(this, TextStyles.TEXT_IMG); - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - try { - return createImageChooser(box, style); - } catch (RepositoryException e) { - throw new CmsException("Cannot create image chooser", e); - } - } else { - return createLabel(box, style); - } - } - - @Override - public synchronized void stopEditing() { - super.stopEditing(); - fileUploadListener = null; - } - - @Override - protected synchronized Boolean load(Control lbl) { - try { - Node imgNode = getNode(); - boolean loaded = imageManager.load(imgNode, lbl, - getPreferredImageSize()); - // getParent().layout(); - return loaded; - } catch (RepositoryException e) { - throw new CmsException("Cannot load " + getNodeId() - + " from image manager", e); - } - } - - protected Control createImageChooser(Composite box, String style) - throws RepositoryException { - // FileDialog fileDialog = new FileDialog(getShell()); - // fileDialog.open(); - // String fileName = fileDialog.getFileName(); - CmsImageManager imageManager = CmsUtils.getCmsView().getImageManager(); - Node node = getNode(); - JcrFileUploadReceiver receiver = new JcrFileUploadReceiver( - node.getParent(), node.getName() + '[' + node.getIndex() + ']', - imageManager); - if (currentUploadHandler != null) - currentUploadHandler.dispose(); - currentUploadHandler = prepareUpload(receiver); - final ServerPushSession pushSession = new ServerPushSession(); - final FileUpload fileUpload = new FileUpload(box, SWT.NONE); - CmsUtils.style(fileUpload, style); - fileUpload.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -9158471843941668562L; - - @Override - public void widgetSelected(SelectionEvent e) { - pushSession.start(); - fileUpload.submit(currentUploadHandler.getUploadUrl()); - } - }); - return fileUpload; - } - - protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { - final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); - if (fileUploadListener != null) - uploadHandler.addUploadListener(fileUploadListener); - return uploadHandler; - } - - @Override - public Section getSection() { - return section; - } - - public void setFileUploadListener(FileUploadListener fileUploadListener) { - this.fileUploadListener = fileUploadListener; - if (currentUploadHandler != null) - currentUploadHandler.addUploadListener(fileUploadListener); - } - - @Override - public Node getItem() throws RepositoryException { - return getNode(); - } - - @Override - public String getPartId() { - return getNodeId(); - } - - @Override - public String toString() { - return "Img #" + getPartId(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/Paragraph.java b/org.argeo.cms/src/org/argeo/cms/text/Paragraph.java deleted file mode 100644 index a7a7964f4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/Paragraph.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.Section; -import org.argeo.cms.viewers.SectionPart; -import org.argeo.cms.widgets.EditableText; - -public class Paragraph extends EditableText implements SectionPart { - private static final long serialVersionUID = 3746457776229542887L; - - private final TextSection section; - - public Paragraph(TextSection section, int style, Node node) - throws RepositoryException { - super(section, style, node); - this.section = section; - CmsUtils.style(this, TextStyles.TEXT_PARAGRAPH); - } - - public Section getSection() { - return section; - } - - @Override - public String getPartId() { - return getNodeId(); - } - - @Override - public Node getItem() throws RepositoryException { - return getNode(); - } - - @Override - public String toString() { - return "Paragraph #" + getPartId(); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/StandardTextEditor.java b/org.argeo.cms/src/org/argeo/cms/text/StandardTextEditor.java deleted file mode 100644 index 4ce8d1fd9..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/StandardTextEditor.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.cms.text; - -import static javax.jcr.Property.JCR_TITLE; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsTypes; -import org.argeo.cms.internal.text.AbstractTextViewer; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.Section; -import org.eclipse.swt.widgets.Composite; - -/** Text editor where sections and subsections can be managed by the user. */ -public class StandardTextEditor extends AbstractTextViewer { - private static final long serialVersionUID = 6049661610883342325L; - - public StandardTextEditor(Composite parent, int style, Node textNode, - CmsEditable cmsEditable) throws RepositoryException { - super(new TextSection(parent, style, textNode), style, cmsEditable); - refresh(); - getMainSection().setLayoutData(CmsUtils.fillWidth()); - } - - @Override - protected void initModel(Node textNode) throws RepositoryException { - if (isFlat()) - textNode.addNode(CMS_P).addMixin(CmsTypes.CMS_STYLED); - else - textNode.setProperty(JCR_TITLE, textNode.getName()); - } - - @Override - protected Boolean isModelInitialized(Node textNode) - throws RepositoryException { - return textNode.hasProperty(Property.JCR_TITLE) - || textNode.hasNode(CMS_P) - || (!isFlat() && textNode.hasNode(CMS_H)); - } - - @Override - public Section getMainSection() { - // TODO Auto-generated method stub - return super.getMainSection(); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/TextEditorHeader.java b/org.argeo.cms/src/org/argeo/cms/text/TextEditorHeader.java deleted file mode 100644 index e70a4d06e..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/TextEditorHeader.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.argeo.cms.text; - -import java.util.Observable; -import java.util.Observer; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; - -/** Adds editing capabilities to a page editing text */ -public class TextEditorHeader implements SelectionListener, Observer { - private static final long serialVersionUID = 4186756396045701253L; - - private final CmsEditable cmsEditable; - private Button publish; - - private Composite parent; - private Composite display; - private Object layoutData; - - public TextEditorHeader(CmsEditable cmsEditable, Composite parent, int style) { - this.cmsEditable = cmsEditable; - this.parent = parent; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - refresh(); - } - - protected void refresh() { - if (display != null && !display.isDisposed()) - display.dispose(); - display = null; - publish = null; - if (cmsEditable.isEditing()) { - display = new Composite(parent, SWT.NONE); - // display.setBackgroundMode(SWT.INHERIT_NONE); - display.setLayoutData(layoutData); - display.setLayout(CmsUtils.noSpaceGridLayout()); - CmsUtils.style(display, TextStyles.TEXT_EDITOR_HEADER); - publish = new Button(display, SWT.FLAT | SWT.PUSH); - publish.setText(getPublishButtonLabel()); - CmsUtils.style(publish, TextStyles.TEXT_EDITOR_HEADER); - publish.addSelectionListener(this); - display.moveAbove(null); - } - parent.layout(); - } - - private String getPublishButtonLabel() { - if (cmsEditable.isEditing()) - return "Publish"; - else - return "Edit"; - } - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.getSource() == publish) { - if (cmsEditable.isEditing()) { - cmsEditable.stopEditing(); - } else { - cmsEditable.startEditing(); - } - // publish.setText(getPublishButtonLabel()); - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) { - // publish.setText(getPublishButtonLabel()); - refresh(); - } - } - - public void setLayoutData(Object layoutData) { - this.layoutData = layoutData; - if (display != null && !display.isDisposed()) - display.setLayoutData(layoutData); - } - -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/text/TextInterpreter.java b/org.argeo.cms/src/org/argeo/cms/text/TextInterpreter.java deleted file mode 100644 index f39a2b329..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/TextInterpreter.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Item; - -/** Convert from/to data layer to/from presentation layer. */ -public interface TextInterpreter { - public String raw(Item item); - - public String read(Item item); - - public void write(Item item, String content); -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/TextSection.java b/org.argeo.cms/src/org/argeo/cms/text/TextSection.java deleted file mode 100644 index ac93e4b65..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/TextSection.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsNames; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.Section; -import org.eclipse.swt.widgets.Composite; - -public class TextSection extends Section implements CmsNames { - private static final long serialVersionUID = -8625209546243220689L; - private String defaultTextStyle = TextStyles.TEXT_DEFAULT; - private String titleStyle; - - public TextSection(Composite parent, int style, Node node) - throws RepositoryException { - this(parent, findSection(parent), style, node); - } - - public TextSection(TextSection section, int style, Node node) - throws RepositoryException { - this(section, section.getParentSection(), style, node); - } - - private TextSection(Composite parent, Section parentSection, int style, - Node node) throws RepositoryException { - super(parent, parentSection, style, node); - CmsUtils.style(this, TextStyles.TEXT_SECTION); - } - - public String getDefaultTextStyle() { - return defaultTextStyle; - } - - public String getTitleStyle() { - if (titleStyle != null) - return titleStyle; - // TODO make base H styles configurable - Integer relativeDepth = getRelativeDepth(); - return relativeDepth == 0 ? TextStyles.TEXT_TITLE : TextStyles.TEXT_H - + relativeDepth; - } - - public void setDefaultTextStyle(String defaultTextStyle) { - this.defaultTextStyle = defaultTextStyle; - } - - public void setTitleStyle(String titleStyle) { - this.titleStyle = titleStyle; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/TextStyles.java b/org.argeo.cms/src/org/argeo/cms/text/TextStyles.java deleted file mode 100644 index 44c3ad054..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/TextStyles.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.text; - -/** Styles references in the CSS. */ -public interface TextStyles { - /** The whole page area */ - public final static String TEXT_AREA = "text_area"; - /** Area providing controls for editing text */ - public final static String TEXT_EDITOR_HEADER = "text_editor_header"; - /** The styled composite for editing the text */ - public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite"; - /** A section */ - public final static String TEXT_SECTION = "text_section"; - /** A paragraph */ - public final static String TEXT_PARAGRAPH = "text_paragraph"; - /** An image */ - public final static String TEXT_IMG = "text_img"; - /** The dialog to edit styled paragraph */ - public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog"; - - /* - * DEFAULT TEXT STYLES - */ - /** Default style for text body */ - public final static String TEXT_DEFAULT = "text_default"; - /** Fixed-width, typically code */ - public final static String TEXT_PRE = "text_pre"; - /** Quote */ - public final static String TEXT_QUOTE = "text_quote"; - /** Title */ - public final static String TEXT_TITLE = "text_title"; - /** Header (to be dynamically completed with the depth, e.g. text_h1) */ - public final static String TEXT_H = "text_h"; - - /** Default style for images */ - public final static String TEXT_IMAGE = "text_image"; - -} diff --git a/org.argeo.cms/src/org/argeo/cms/text/WikiPage.java b/org.argeo.cms/src/org/argeo/cms/text/WikiPage.java deleted file mode 100644 index 38af381a5..000000000 --- a/org.argeo.cms/src/org/argeo/cms/text/WikiPage.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.cms.text; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsNames; -import org.argeo.cms.CmsTypes; -import org.argeo.cms.CmsUiProvider; -import org.argeo.cms.util.CmsLink; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.viewers.JcrVersionCmsEditable; -import org.argeo.cms.widgets.ScrolledPage; -import org.argeo.jcr.JcrUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Display the text of the context, and provide an editor if the user can edit. */ -public class WikiPage implements CmsUiProvider, CmsNames { - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - CmsEditable cmsEditable = new JcrVersionCmsEditable(context); - if (cmsEditable.canEdit()) - new TextEditorHeader(cmsEditable, parent, SWT.NONE) - .setLayoutData(CmsUtils.fillWidth()); - - ScrolledPage page = new ScrolledPage(parent, SWT.NONE); - page.setLayout(CmsUtils.noSpaceGridLayout()); - GridData textGd = CmsUtils.fillAll(); - page.setLayoutData(textGd); - - if (context.isNodeType(CmsTypes.CMS_TEXT)) { - new StandardTextEditor(page, SWT.NONE, context, cmsEditable); - } else if (context.isNodeType(NodeType.NT_FOLDER) - || context.getPath().equals("/")) { - parent.setBackgroundMode(SWT.INHERIT_NONE); - if (context.getSession().hasPermission(context.getPath(), - Session.ACTION_ADD_NODE)) { - Node indexNode = JcrUtils.getOrAdd(context, CMS_INDEX, - CmsTypes.CMS_TEXT); - new StandardTextEditor(page, SWT.NONE, indexNode, cmsEditable); - textGd.heightHint = 400; - - for (NodeIterator ni = context.getNodes(); ni.hasNext();) { - Node textNode = ni.nextNode(); - if (textNode.isNodeType(NodeType.NT_FOLDER)) - new CmsLink(textNode.getName() + "/", - textNode.getPath()).createUi(parent, textNode); - } - for (NodeIterator ni = context.getNodes(); ni.hasNext();) { - Node textNode = ni.nextNode(); - if (textNode.isNodeType(CmsTypes.CMS_TEXT) - && !textNode.getName().equals(CMS_INDEX)) - new CmsLink(textNode.getName(), textNode.getPath()) - .createUi(parent, textNode); - } - } - } - return page; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/ui/UxContext.java b/org.argeo.cms/src/org/argeo/cms/ui/UxContext.java deleted file mode 100644 index f03b88bbb..000000000 --- a/org.argeo.cms/src/org/argeo/cms/ui/UxContext.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.ui; - -public interface UxContext { - boolean isPortrait(); - boolean isLandscape(); - boolean isSquare(); - - boolean isSmall(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/BundleResourceLoader.java b/org.argeo.cms/src/org/argeo/cms/util/BundleResourceLoader.java deleted file mode 100644 index cda00efcd..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/BundleResourceLoader.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.argeo.cms.util; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */ -public class BundleResourceLoader implements ResourceLoader { - private final BundleContext bundleContext; - - public BundleResourceLoader(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - @Override - public InputStream getResourceAsStream(String resourceName) - throws IOException { - // TODO deal with other bundles - Bundle bundle = bundleContext.getBundle(); - // String location = - // bundle.getLocation().substring("initial@reference:".length()); - // if (location.startsWith("file:")) { - // Path path = null; - // try { - // path = Paths.get(new URI(location)); - // } catch (URISyntaxException e) { - // e.printStackTrace(); - // } - // if (path != null) { - // Path resourcePath = path.resolve(resourceName); - // if (Files.exists(resourcePath)) - // return Files.newInputStream(resourcePath); - // } - // } - URL res = bundle.getResource(resourceName); - if (res == null) - throw new CmsException("Resource " + resourceName - + " not found in bundle " + bundle.getSymbolicName()); - return res.openStream(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CmsLink.java b/org.argeo.cms/src/org/argeo/cms/util/CmsLink.java deleted file mode 100644 index 14f3755ff..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/CmsLink.java +++ /dev/null @@ -1,274 +0,0 @@ -package org.argeo.cms.util; - -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.jcr.Node; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsStyles; -import org.argeo.cms.CmsUiProvider; -import org.eclipse.gemini.blueprint.context.BundleContextAware; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; -import org.springframework.beans.factory.InitializingBean; - -/** A link to an internal or external location. */ -public class CmsLink implements CmsUiProvider, InitializingBean, - BundleContextAware { - private final static Log log = LogFactory.getLog(CmsLink.class); - - private String label; - private String custom; - private String target; - private String image; - private MouseListener mouseListener; - - private int verticalAlignment = SWT.CENTER; - - // internal - // private Boolean isUrl = false; - private Integer imageWidth, imageHeight; - - private BundleContext bundleContext; - - public CmsLink() { - super(); - } - - public CmsLink(String label, String target) { - this(label, target, null); - } - - public CmsLink(String label, String target, String custom) { - super(); - this.label = label; - this.target = target; - this.custom = custom; - afterPropertiesSet(); - } - - @Override - public void afterPropertiesSet() { - // if (target != null) { - // if (target.startsWith("/")) { - // isUrl = true; - // } else { - // try { - // new URL(target); - // isUrl = true; - // } catch (MalformedURLException e1) { - // isUrl = false; - // } - // } - // } - - if (image != null) { - ImageData image = loadImage(); - imageWidth = image.width; - imageHeight = image.height; - } - } - - /** @return {@link Composite} with a single {@link Label} child. */ - @Override - public Control createUi(final Composite parent, Node context) { - Composite comp = new Composite(parent, SWT.BOTTOM); - comp.setLayout(CmsUtils.noSpaceGridLayout()); - - Label link = new Label(comp, SWT.NONE); - link.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); - GridData layoutData = new GridData(SWT.CENTER, verticalAlignment, true, - true); - if (image != null) { - layoutData.heightHint = imageHeight; - if (label == null) - layoutData.widthHint = imageWidth; - } - - link.setLayoutData(layoutData); - if (custom != null) { - comp.setData(RWT.CUSTOM_VARIANT, custom); - link.setData(RWT.CUSTOM_VARIANT, custom); - } else { - comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); - link.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); - } - - // label - StringBuilder labelText = new StringBuilder(); - if (target != null) { - labelText - .append(""); - } - if (image != null) { - registerImageIfNeeded(); - String imageLocation = RWT.getResourceManager().getLocation(image); - labelText.append(""); - - // final Image img = loadImage(parent.getDisplay()); - // link.setImage(img); - // link.addDisposeListener(new DListener(img)); - } - - if (label != null) { - // link.setText(label); - labelText.append(' ').append(label); - } - - if (target != null) - labelText.append(""); - - link.setText(labelText.toString()); - - // link.setCursor(link.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); - // CmsSession cmsSession = (CmsSession) parent.getDisplay().getData( - // CmsSession.KEY); - if (mouseListener != null) - link.addMouseListener(mouseListener); - - return comp; - } - - private void registerImageIfNeeded() { - ResourceManager resourceManager = RWT.getResourceManager(); - if (!resourceManager.isRegistered(image)) { - URL res = getImageUrl(); - InputStream inputStream = null; - try { - IOUtils.closeQuietly(inputStream); - inputStream = res.openStream(); - resourceManager.register(image, inputStream); - if (log.isTraceEnabled()) - log.trace("Registered image " + image); - } catch (Exception e) { - throw new CmsException("Cannot load image " + image, e); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - } - - private ImageData loadImage() { - URL url = getImageUrl(); - ImageData result = null; - InputStream inputStream = null; - try { - inputStream = url.openStream(); - result = new ImageData(inputStream); - if (log.isTraceEnabled()) - log.trace("Loaded image " + image); - } catch (Exception e) { - throw new CmsException("Cannot load image " + image, e); - } finally { - IOUtils.closeQuietly(inputStream); - } - return result; - } - - private URL getImageUrl() { - URL url; - try { - // pure URL - url = new URL(image); - } catch (MalformedURLException e1) { - // in OSGi bundle - if (bundleContext == null) - throw new CmsException("No bundle context available"); - url = bundleContext.getBundle().getResource(image); - } - - if (url == null) - throw new CmsException("No image " + image + " available."); - - return url; - } - - public void setLabel(String label) { - this.label = label; - } - - public void setCustom(String custom) { - this.custom = custom; - } - - public void setTarget(String target) { - this.target = target; - // try { - // new URL(target); - // isUrl = true; - // } catch (MalformedURLException e1) { - // isUrl = false; - // } - } - - public void setImage(String image) { - this.image = image; - } - - @Override - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setMouseListener(MouseListener mouseListener) { - this.mouseListener = mouseListener; - } - - public void setvAlign(String vAlign) { - if ("bottom".equals(vAlign)) { - verticalAlignment = SWT.BOTTOM; - } else if ("top".equals(vAlign)) { - verticalAlignment = SWT.TOP; - } else if ("center".equals(vAlign)) { - verticalAlignment = SWT.CENTER; - } else { - throw new CmsException("Unsupported vertical allignment " + vAlign - + " (must be: top, bottom or center)"); - } - } - - // private class MListener extends MouseAdapter { - // private static final long serialVersionUID = 3634864186295639792L; - // - // @Override - // public void mouseDown(MouseEvent e) { - // if (e.button == 1) { - // } - // } - // } - // - // private class DListener implements DisposeListener { - // private static final long serialVersionUID = -3808587499269394812L; - // private final Image img; - // - // public DListener(Image img) { - // super(); - // this.img = img; - // } - // - // @Override - // public void widgetDisposed(DisposeEvent event) { - // img.dispose(); - // } - // - // } -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java b/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java deleted file mode 100644 index e5d30262c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java +++ /dev/null @@ -1,248 +0,0 @@ -package org.argeo.cms.util; - -import static org.argeo.cms.internal.kernel.KernelConstants.WEBDAV_PRIVATE; -import static org.argeo.cms.internal.kernel.KernelConstants.WEBDAV_PUBLIC; -import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE; - -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsView; -import org.argeo.cms.auth.AuthConstants; -import org.argeo.eclipse.ui.specific.UiContext; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Widget; - -/** Static utilities for the CMS framework. */ -public class CmsUtils implements CmsConstants { - private final static Log log = LogFactory.getLog(CmsUtils.class); - - /** - * The CMS view related to this display, or null if none is available from - * this call. - */ - public static CmsView getCmsView() { - return UiContext.getData(CmsView.KEY); - } - - public static StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - public static String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(getDataPath(node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - } - - public static String getDataPath(Node node) throws RepositoryException { - assert node != null; - String userId = node.getSession().getUserID(); - if (log.isTraceEnabled()) - log.trace(userId + " : " + node.getPath()); - StringBuilder buf = new StringBuilder(); - boolean isAnonymous = userId.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS); - if (isAnonymous) - buf.append(WEBDAV_PUBLIC); - else - buf.append(WEBDAV_PRIVATE); - // TODO convey repo alias vie repository properties - return buf.append('/').append(ALIAS_NODE).append('/').append(node.getSession().getWorkspace().getName()) - .append(node.getPath()).toString(); - } - - public static String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append('/').append('!').append(node.getPath()); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - // return request.getRequestURL().append('!').append(node.getPath()) - // .toString(); - } - - /** @deprecated Use rowData16px() instead. GridData should not be reused. */ - @Deprecated - public static RowData ROW_DATA_16px = new RowData(16, 16); - - public static GridLayout noSpaceGridLayout() { - return noSpaceGridLayout(new GridLayout()); - } - - public static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - // - // GRID DATA - // - public static GridData fillWidth() { - return grabWidth(SWT.FILL, SWT.FILL); - } - - public static GridData fillAll() { - return new GridData(SWT.FILL, SWT.FILL, true, true); - } - - public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { - return new GridData(horizontalAlignment, horizontalAlignment, true, false); - } - - public static RowData rowData16px() { - return new RowData(16, 16); - } - - /** Style widget */ - public static void style(Widget widget, String style) { - widget.setData(CmsConstants.STYLE, style); - } - - /** Enable markups on widget */ - public static void markup(Widget widget) { - widget.setData(CmsConstants.MARKUP, true); - } - - public static void setItemHeight(Table table, int height) { - table.setData(CmsConstants.ITEM_HEIGHT, height); - } - - /** @return the path or null if not instrumented */ - public static String getDataPath(Widget widget) { - // JCR item - Object data = widget.getData(); - if (data != null && data instanceof Item) { - try { - return ((Item) data).getPath(); - } catch (RepositoryException e) { - throw new CmsException("Cannot find data path of " + data + " for " + widget); - } - } - - // JCR path - data = widget.getData(Property.JCR_PATH); - if (data != null) - return data.toString(); - - return null; - } - - /** Dispose all children of a Composite */ - public static void clear(Composite composite) { - for (Control child : composite.getChildren()) - child.dispose(); - } - - // - // JCR - // - public static Node getOrAddEmptyFile(Node parent, Enum child) throws RepositoryException { - if (has(parent, child)) - return child(parent, child); - return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]); - } - - public static Node child(Node parent, Enum en) throws RepositoryException { - return parent.getNode(en.name()); - } - - public static Boolean has(Node parent, Enum en) throws RepositoryException { - return parent.hasNode(en.name()); - } - - public static Node getOrAdd(Node parent, Enum en) throws RepositoryException { - return getOrAdd(parent, en, null); - } - - public static Node getOrAdd(Node parent, Enum en, String primaryType) throws RepositoryException { - if (has(parent, en)) - return child(parent, en); - else if (primaryType == null) - return parent.addNode(en.name()); - else - return parent.addNode(en.name(), primaryType); - } - - // IMAGES - public static String img(String src, String width, String height) { - return imgBuilder(src, width, height).append("/>").toString(); - } - - public static String img(String src, Point size) { - return img(src, Integer.toString(size.x), Integer.toString(size.y)); - } - - public static StringBuilder imgBuilder(String src, String width, String height) { - return new StringBuilder(64).append("not - * CmsLogin#createContent(), since it would lead to a stack overflow. - */ - protected void createLoginPage(Composite parent, CmsLogin login) { - login.defaultCreateContents(parent); - } - - protected void extendsCredentialsBlock(Composite credentialsBlock, - Locale selectedLocale, SelectionListener loginSelectionListener) { - - } - - @Override - public void navigateTo(String state) { - // TODO Auto-generated method stub - - } - - @Override - public void authChange(LoginContext loginContext) { - this.loginContext = loginContext; - } - - @Override - public void logout() { - if (loginContext == null) - throw new CmsException("Login context should not bet null"); - try { - loginContext.logout(); - } catch (LoginException e) { - throw new CmsException("Cannot log out", e); - } - } - - @Override - public final Subject getSubject() { - return subject; - } - - @Override - public void exception(Throwable e) { - // TODO Auto-generated method stub - - } - - @Override - public CmsImageManager getImageManager() { - // TODO Auto-generated method stub - return null; - } - - @Override - public UxContext getUxContext() { - return uxContext; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/MenuLink.java b/org.argeo.cms/src/org/argeo/cms/util/MenuLink.java deleted file mode 100644 index d491d3ca2..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/MenuLink.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.util; - -import org.argeo.cms.CmsStyles; - -/** - * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on - * a {@link CmsLink} when simple menus are used. - */ -public class MenuLink extends CmsLink { - public MenuLink() { - setCustom(CmsStyles.CMS_MENU_LINK); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/OpenUserMenu.java b/org.argeo.cms/src/org/argeo/cms/util/OpenUserMenu.java deleted file mode 100644 index bb2bb3990..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/OpenUserMenu.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.cms.util; - -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.widgets.Control; - -/** Open the user menu when clicked */ -public class OpenUserMenu extends MouseAdapter { - private static final long serialVersionUID = 3634864186295639792L; - - @Override - public void mouseDown(MouseEvent e) { - if (e.button == 1) { - new UserMenu((Control) e.getSource()); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleApp.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleApp.java deleted file mode 100644 index 39e75070b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleApp.java +++ /dev/null @@ -1,376 +0,0 @@ -package org.argeo.cms.util; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.Privilege; -import javax.jcr.version.VersionManager; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsUiProvider; -import org.argeo.cms.LifeCycleUiProvider; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.EntryPoint; -import org.eclipse.rap.rwt.application.EntryPointFactory; -import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -/** A basic generic app based on {@link SimpleErgonomics}. */ -public class SimpleApp implements CmsConstants, ApplicationConfiguration { - private final static Log log = LogFactory.getLog(SimpleApp.class); - - private String contextName = null; - - private Map> branding = new HashMap>(); - private Map> styleSheets = new HashMap>(); - - private List resources = new ArrayList(); - - private BundleContext bundleContext; - - private Repository repository; - private String workspace = null; - private String jcrBasePath = "/"; - private List roPrincipals = Arrays.asList("anonymous", "everyone"); - private List rwPrincipals = Arrays.asList("everyone"); - - private CmsUiProvider header; - private Map pages = new LinkedHashMap(); - - private Integer headerHeight = 40; - - private ServiceRegistration appReg; - - public void configure(Application application) { - try { - StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader( - bundleContext); - BundleResourceLoader bundleRL = new BundleResourceLoader( - bundleContext); - - application.setOperationMode(OperationMode.SWT_COMPATIBILITY); - // application.setOperationMode(OperationMode.JEE_COMPATIBILITY); - - application.setExceptionHandler(new CmsExceptionHandler()); - - // loading animated gif - application.addResource(LOADING_IMAGE, - createResourceLoader(LOADING_IMAGE)); - // empty image - application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE)); - - for (String resource : resources) { - application.addResource(resource, bundleRL); - if (log.isTraceEnabled()) - log.trace("Resource " + resource); - } - - Map defaultBranding = null; - if (branding.containsKey("*")) - defaultBranding = branding.get("*"); - - // entry points - for (String page : pages.keySet()) { - Map properties = defaultBranding != null ? new HashMap( - defaultBranding) : new HashMap(); - if (branding.containsKey(page)) { - properties.putAll(branding.get(page)); - } - // favicon - if (properties.containsKey(WebClient.FAVICON)) { - String faviconRelPath = properties.get(WebClient.FAVICON); - application.addResource(faviconRelPath, - new BundleResourceLoader(bundleContext)); - if (log.isTraceEnabled()) - log.trace("Favicon " + faviconRelPath); - - } - - // page title - if (!properties.containsKey(WebClient.PAGE_TITLE)) { - if (page.length() > 0) - properties.put( - WebClient.PAGE_TITLE, - Character.toUpperCase(page.charAt(0)) - + page.substring(1)); - } - - // default body HTML - if (!properties.containsKey(WebClient.BODY_HTML)) - properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY); - - // - // ADD ENTRY POINT - // - application.addEntryPoint("/" + page, new CmsEntryPointFactory( - pages.get(page), repository, workspace, properties), - properties); - log.info("Page /" + page); - } - - // stylesheets - for (String themeId : styleSheets.keySet()) { - List cssLst = styleSheets.get(themeId); - if (log.isDebugEnabled()) - log.debug("Theme " + themeId); - for (String css : cssLst) { - application.addStyleSheet(themeId, css, styleSheetRL); - if (log.isTraceEnabled()) - log.trace(" CSS " + css); - } - - } - } catch (RuntimeException e) { - // Easier access to initialisation errors - log.error("Unexpected exception when configuring RWT application.", - e); - throw e; - } - } - - public void init() throws RepositoryException { - Session session = null; - try { - session = JcrUtils.loginOrCreateWorkspace(repository, workspace); - VersionManager vm = session.getWorkspace().getVersionManager(); - if (!vm.isCheckedOut("/")) - vm.checkout("/"); - JcrUtils.mkdirs(session, jcrBasePath); - for (String principal : rwPrincipals) - JcrUtils.addPrivilege(session, jcrBasePath, principal, - Privilege.JCR_WRITE); - for (String principal : roPrincipals) - JcrUtils.addPrivilege(session, jcrBasePath, principal, - Privilege.JCR_READ); - - for (String pageName : pages.keySet()) { - try { - initPage(session, pages.get(pageName)); - session.save(); - } catch (Exception e) { - throw new CmsException( - "Cannot initialize page " + pageName, e); - } - } - - } finally { - JcrUtils.logoutQuietly(session); - } - - // publish to OSGi - register(); - } - - protected void initPage(Session adminSession, CmsUiProvider page) - throws RepositoryException { - if (page instanceof LifeCycleUiProvider) - ((LifeCycleUiProvider) page).init(adminSession); - } - - public void destroy() { - for (String pageName : pages.keySet()) { - try { - CmsUiProvider page = pages.get(pageName); - if (page instanceof LifeCycleUiProvider) - ((LifeCycleUiProvider) page).destroy(); - } catch (Exception e) { - log.error("Cannot destroy page " + pageName, e); - } - } - } - - protected void register() { - Hashtable props = new Hashtable(); - if (contextName != null) - props.put("contextName", contextName); - appReg = bundleContext.registerService(ApplicationConfiguration.class, - this, props); - if (log.isDebugEnabled()) - log.debug("Registered " + (contextName == null ? "/" : contextName)); - } - - protected void unregister() { - appReg.unregister(); - if (log.isDebugEnabled()) - log.debug("Unregistered " - + (contextName == null ? "/" : contextName)); - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - public void setHeader(CmsUiProvider header) { - this.header = header; - } - - public void setPages(Map pages) { - this.pages = pages; - } - - public void setJcrBasePath(String basePath) { - this.jcrBasePath = basePath; - } - - public void setRoPrincipals(List roPrincipals) { - this.roPrincipals = roPrincipals; - } - - public void setRwPrincipals(List rwPrincipals) { - this.rwPrincipals = rwPrincipals; - } - - public void setHeaderHeight(Integer headerHeight) { - this.headerHeight = headerHeight; - } - - public void setBranding(Map> branding) { - this.branding = branding; - } - - public void setStyleSheets(Map> styleSheets) { - this.styleSheets = styleSheets; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setResources(List resources) { - this.resources = resources; - } - - public void setContextName(String contextName) { - this.contextName = contextName; - } - - class CmsExceptionHandler implements ExceptionHandler { - - @Override - public void handleException(Throwable throwable) { - // TODO be smarter - CmsUtils.getCmsView().exception(throwable); - } - - } - - private class CmsEntryPointFactory implements EntryPointFactory { - private final CmsUiProvider page; - private final Repository repository; - private final String workspace; - private final Map properties; - - public CmsEntryPointFactory(CmsUiProvider page, Repository repository, - String workspace, Map properties) { - this.page = page; - this.repository = repository; - this.workspace = workspace; - this.properties = properties; - } - - @Override - public EntryPoint create() { - SimpleErgonomics entryPoint = new SimpleErgonomics(repository, - workspace, jcrBasePath, page, properties) { - - @Override - protected void createAdminArea(Composite parent) { - Composite adminArea = new Composite(parent, SWT.NONE); - adminArea.setLayout(new FillLayout()); - Button refresh = new Button(adminArea, SWT.PUSH); - refresh.setText("Reload App"); - refresh.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -7671999525536351366L; - - @Override - public void widgetSelected(SelectionEvent e) { - long timeBeforeReload = 1000; - RWT.getClient() - .getService(JavaScriptExecutor.class) - .execute( - "setTimeout(function() { " - + "location.reload();" - + "}," + timeBeforeReload - + ");"); - reloadApp(); - } - }); - } - }; - // entryPoint.setState(""); - entryPoint.setHeader(header); - entryPoint.setHeaderHeight(headerHeight); - // CmsSession.current.set(entryPoint); - return entryPoint; - } - - private void reloadApp() { - new Thread("Refresh app") { - @Override - public void run() { - unregister(); - register(); - } - }.start(); - } - } - - private static ResourceLoader createResourceLoader(final String resourceName) { - return new ResourceLoader() { - public InputStream getResourceAsStream(String resourceName) - throws IOException { - return getClass().getClassLoader().getResourceAsStream( - resourceName); - } - }; - } - - // private static ResourceLoader createUrlResourceLoader(final URL url) { - // return new ResourceLoader() { - // public InputStream getResourceAsStream(String resourceName) - // throws IOException { - // return url.openStream(); - // } - // }; - // } - - /* - * TEXTS - */ - private static String DEFAULT_LOADING_BODY = "" - + "" - + ""; -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleCmsHeader.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleCmsHeader.java deleted file mode 100644 index aa1bb7340..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleCmsHeader.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.argeo.cms.util; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsStyles; -import org.argeo.cms.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** A header in three parts */ -public class SimpleCmsHeader implements CmsUiProvider { - private List lead = new ArrayList(); - private List center = new ArrayList(); - private List end = new ArrayList(); - - private Boolean subPartsSameWidth = false; - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Composite header = new Composite(parent, SWT.NONE); - header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER); - header.setBackgroundMode(SWT.INHERIT_DEFAULT); - header.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(3, false))); - - configurePart(context, header, lead); - configurePart(context, header, center); - configurePart(context, header, end); - return header; - } - - protected void configurePart(Node context, Composite parent, - List partProviders) throws RepositoryException { - final int style; - final String custom; - if (lead == partProviders) { - style = SWT.LEAD; - custom = CmsStyles.CMS_HEADER_LEAD; - } else if (center == partProviders) { - style = SWT.CENTER; - custom = CmsStyles.CMS_HEADER_CENTER; - } else if (end == partProviders) { - style = SWT.END; - custom = CmsStyles.CMS_HEADER_END; - } else { - throw new CmsException("Unsupported part providers " - + partProviders); - } - - Composite part = new Composite(parent, SWT.NONE); - part.setData(RWT.CUSTOM_VARIANT, custom); - GridData gridData = new GridData(style, SWT.FILL, true, true); - part.setLayoutData(gridData); - part.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(partProviders - .size(), subPartsSameWidth))); - for (CmsUiProvider uiProvider : partProviders) { - Control subPart = uiProvider.createUi(part, context); - subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, - true)); - } - } - - public void setLead(List lead) { - this.lead = lead; - } - - public void setCenter(List center) { - this.center = center; - } - - public void setEnd(List end) { - this.end = end; - } - - public void setSubPartsSameWidth(Boolean subPartsSameWidth) { - this.subPartsSameWidth = subPartsSameWidth; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleDynamicPages.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleDynamicPages.java deleted file mode 100644 index b6155cf31..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleDynamicPages.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.argeo.cms.util; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsUiProvider; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class SimpleDynamicPages implements CmsUiProvider { - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - if (context == null) - throw new CmsException("Context cannot be null"); - parent.setLayout(new GridLayout(2, false)); - - // parent - if (!context.getPath().equals("/")) { - new CmsLink("..", context.getParent().getPath()).createUi(parent, - context); - new Label(parent, SWT.NONE).setText(context.getParent() - .getPrimaryNodeType().getName()); - } - - // context - Label contextL = new Label(parent, SWT.NONE); - contextL.setData(RWT.MARKUP_ENABLED, true); - contextL.setText("" + context.getName() + ""); - new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() - .getName()); - - // children - // Label childrenL = new Label(parent, SWT.NONE); - // childrenL.setData(RWT.MARKUP_ENABLED, true); - // childrenL.setText("Children:"); - // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, - // false, 2, 1)); - - for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { - Node child = nIt.nextNode(); - new CmsLink(child.getName(), child.getPath()).createUi(parent, - context); - - new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() - .getName()); - } - - // properties - // Label propsL = new Label(parent, SWT.NONE); - // propsL.setData(RWT.MARKUP_ENABLED, true); - // propsL.setText("Properties:"); - // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, - // 2, 1)); - for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { - Property property = pIt.nextProperty(); - - Label label = new Label(parent, SWT.NONE); - label.setText(property.getName()); - label.setToolTipText(JcrUtils - .getPropertyDefinitionAsString(property)); - - new Label(parent, SWT.NONE).setText(getPropAsString(property)); - } - - return null; - } - - private String getPropAsString(Property property) - throws RepositoryException { - String result = ""; - DateFormat timeFormatter = new SimpleDateFormat(""); - if (property.isMultiple()) { - result = getMultiAsString(property, ", "); - } else { - Value value = property.getValue(); - if (value.getType() == PropertyType.BINARY) - result = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) - throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleErgonomics.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleErgonomics.java deleted file mode 100644 index e7192af0f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleErgonomics.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.argeo.cms.util; - -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.AbstractCmsEntryPoint; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsImageManager; -import org.argeo.cms.CmsStyles; -import org.argeo.cms.CmsUiProvider; -import org.argeo.cms.internal.ImageManagerImpl; -import org.argeo.cms.ui.UxContext; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Simple header/body ergonomics. */ -public class SimpleErgonomics extends AbstractCmsEntryPoint { - private final static Log log = LogFactory.getLog(SimpleErgonomics.class); - - private boolean uiInitialized = false; - private Composite headerArea; - private Composite bodyArea; - private final CmsUiProvider uiProvider; - - private CmsUiProvider header; - private Integer headerHeight = 40; - - private CmsImageManager imageManager = new ImageManagerImpl(); - private UxContext uxContext = null; - - public SimpleErgonomics(Repository repository, String workspace, - String defaultPath, CmsUiProvider uiProvider, - Map factoryProperties) { - super(repository, workspace, defaultPath, factoryProperties); - this.uiProvider = uiProvider; - } - - @Override - protected void initUi(Composite parent) { - parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - parent.setLayout(CmsUtils.noSpaceGridLayout()); - - // createAdminArea(parent); - headerArea = new Composite(parent, SWT.NONE); - headerArea.setLayout(new FillLayout()); - GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false); - headerData.heightHint = headerHeight; - headerArea.setLayoutData(headerData); - - bodyArea = new Composite(parent, SWT.NONE); - bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY); - bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - bodyArea.setLayout(CmsUtils.noSpaceGridLayout()); - uxContext = new SimpleUxContext(); - uiInitialized = true; - refresh(); - } - - @Override - protected void refresh() { - if (!uiInitialized) - return; - if (getState() == null) - setState(""); - refreshHeader(); - refreshBody(); - if (log.isTraceEnabled()) - log.trace("UI refreshed " + getNode()); - } - - protected void createAdminArea(Composite parent) { - } - - protected void refreshHeader() { - for (Control child : headerArea.getChildren()) - child.dispose(); - try { - header.createUi(headerArea, getNode()); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh header", e); - } - headerArea.layout(true, true); - } - - protected void refreshBody() { - // Exception - Throwable exception = getException(); - if (exception != null) { - SystemNotifications systemNotifications = new SystemNotifications( - bodyArea); - systemNotifications.notifyException(exception); - resetException(); - return; - // TODO report - } - - // clear - for (Control child : bodyArea.getChildren()) - child.dispose(); - bodyArea.setLayout(CmsUtils.noSpaceGridLayout()); - - try { - uiProvider.createUi(bodyArea, getNode()); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh body", e); - } - - bodyArea.layout(true, true); - } - - @Override - public UxContext getUxContext() { - return uxContext; - } - - @Override - public CmsImageManager getImageManager() { - return imageManager; - } - - public void setHeader(CmsUiProvider header) { - this.header = header; - } - - public void setHeaderHeight(Integer headerHeight) { - this.headerHeight = headerHeight; - } - - public void setImageManager(CmsImageManager imageManager) { - this.imageManager = imageManager; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleStaticPage.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleStaticPage.java deleted file mode 100644 index 6e09000e9..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleStaticPage.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.util; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsStyles; -import org.argeo.cms.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class SimpleStaticPage implements CmsUiProvider { - private String text; - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Label textC = new Label(parent, SWT.WRAP); - textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT); - textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); - textC.setText(text); - - return textC; - } - - public void setText(String text) { - this.text = text; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/SimpleUxContext.java b/org.argeo.cms/src/org/argeo/cms/util/SimpleUxContext.java deleted file mode 100644 index 6cb37bde4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SimpleUxContext.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.cms.util; - -import org.argeo.cms.ui.UxContext; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Display; - -public class SimpleUxContext implements UxContext { - private Point size; - private Point small = new Point(400, 400); - - public SimpleUxContext() { - this(Display.getCurrent().getBounds()); - } - - public SimpleUxContext(Rectangle rect) { - this.size = new Point(rect.width, rect.height); - } - - public SimpleUxContext(Point size) { - this.size = size; - } - - @Override - public boolean isPortrait() { - return size.x >= size.y; - } - - @Override - public boolean isLandscape() { - return size.x < size.y; - } - - @Override - public boolean isSquare() { - return size.x == size.y; - } - - @Override - public boolean isSmall() { - return size.x <= small.x || size.y <= small.y; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/StyleSheetResourceLoader.java b/org.argeo.cms/src/org/argeo/cms/util/StyleSheetResourceLoader.java deleted file mode 100644 index a7e3b6e84..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/StyleSheetResourceLoader.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -/** {@link ResourceLoader} caching stylesheets. */ -public class StyleSheetResourceLoader implements ResourceLoader { - private final BundleContext bundleContext; - - private Map stylesheets = new LinkedHashMap(); - - public StyleSheetResourceLoader(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - @Override - public InputStream getResourceAsStream(String resourceName) - throws IOException { - if (!stylesheets.containsKey(resourceName)) { - // TODO deal with other bundles - Bundle bundle = bundleContext.getBundle(); - // String location = - // bundle.getLocation().substring("initial@reference:".length()); - // if (location.startsWith("file:")) { - // Path path = null; - // try { - // path = Paths.get(new URI(location)); - // } catch (URISyntaxException e) { - // e.printStackTrace(); - // } - // if (path != null) { - // Path resourcePath = path.resolve(resourceName); - // if (Files.exists(resourcePath)) - // return Files.newInputStream(resourcePath); - // } - // } - URL res = bundle.getResource(resourceName); - if (res == null) - throw new CmsException("Resource " + resourceName - + " not found in bundle " + bundle.getSymbolicName()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(res.openStream(), out); - stylesheets.put(resourceName, new StyleSheet(out.toByteArray())); - } - return new ByteArrayInputStream(stylesheets.get(resourceName).getData()); - // return res.openStream(); - } - - private class StyleSheet { - private byte[] data; - - public StyleSheet(byte[] data) { - super(); - this.data = data; - } - - public byte[] getData() { - return data; - } - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/SystemNotifications.java b/org.argeo.cms/src/org/argeo/cms/util/SystemNotifications.java deleted file mode 100644 index 54e8eb544..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/SystemNotifications.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.argeo.cms.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsStyles; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Shell displaying system notifications such as exceptions */ -public class SystemNotifications extends Shell implements CmsStyles, - MouseListener { - private static final long serialVersionUID = -8129377525216022683L; - - private Control source; - - public SystemNotifications(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - - this.source = source; - - // TODO UI - // setLocation(source.toDisplay(source.getSize().x - getSize().x, - // source.getSize().y)); - setLayout(new GridLayout()); - addMouseListener(this); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - - } - - public void notifyException(Throwable exception) { - Composite pane = this; - - Label lbl = new Label(pane, SWT.NONE); - lbl.setText(exception.getLocalizedMessage() - + (exception instanceof CmsException ? "" : "(" - + exception.getClass().getName() + ")") + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (exception.getCause() != null) - appendCause(pane, exception.getCause()); - - StringBuilder mailToUrl = new StringBuilder("mailto:?"); - try { - mailToUrl.append("subject=").append( - URLEncoder.encode( - "Exception " - + new SimpleDateFormat("yyyy-MM-dd hh:mm") - .format(new Date()), "UTF-8") - .replace("+", "%20")); - - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - IOUtils.closeQuietly(sw); - - // see - // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character - String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace( - "+", "%20"); - mailToUrl.append("&body=").append(encoded); - } catch (UnsupportedEncodingException e) { - mailToUrl.append("&body=").append("Could not encode: ") - .append(e.getMessage()); - } - Label mailTo = new Label(pane, SWT.NONE); - CmsUtils.markup(mailTo); - mailTo.setText("Send details"); - mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - - pack(); - layout(); - - setLocation(source.toDisplay(source.getSize().x - getSize().x, - source.getSize().y - getSize().y)); - open(); - } - - private void appendCause(Composite parent, Throwable e) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(" caused by: " + e.getLocalizedMessage() + " (" - + e.getClass().getName() + ")" + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (e.getCause() != null) - appendCause(parent, e.getCause()); - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - close(); - dispose(); - } - - @Override - public void mouseUp(MouseEvent e) { - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java deleted file mode 100644 index a654dddd4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.cms.util; - -import org.argeo.cms.CmsException; -import org.argeo.cms.widgets.auth.CmsLoginShell; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** The site-related user menu */ -public class UserMenu extends CmsLoginShell { - private final Control source; - - public UserMenu(Control source) { - super(CmsUtils.getCmsView()); - if (source == null) - throw new CmsException("Source control cannot be null."); - this.source = source; - open(); - } - - @Override - protected Shell createShell() { - return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER - | SWT.ON_TOP); - } - - @Override - public void open() { - Shell shell = getShell(); - shell.pack(); - shell.layout(); - shell.setLocation(source.toDisplay(source.getSize().x - - shell.getSize().x, source.getSize().y)); - shell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - closeShell(); - } - }); - super.open(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java deleted file mode 100644 index 1b7ca03f4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.cms.util; - -import javax.jcr.Node; -import javax.security.auth.Subject; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.CmsStyles; -import org.argeo.cms.auth.AuthConstants; -import org.argeo.cms.auth.CurrentUser; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -/** Open the user menu when clicked */ -public class UserMenuLink extends MenuLink { - - public UserMenuLink() { - setCustom(CmsStyles.CMS_USER_MENU_LINK); - } - - @Override - public Control createUi(Composite parent, Node context) { - Subject subject = CmsUtils.getCmsView().getSubject(); - String username = CurrentUser.getUsername(subject); - if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS)) - setLabel(CmsMsg.login.lead()); - else { - setLabel(CurrentUser.getDisplayName(subject)); - } - Label link = (Label) ((Composite) super.createUi(parent, context)) - .getChildren()[0]; - link.addMouseListener(new UserMenuLinkController()); - return link.getParent(); - } - - protected UserMenu createUserMenu(Control source) { - return new UserMenu(source.getParent()); - } - - private class UserMenuLinkController implements MouseListener, - DisposeListener { - private static final long serialVersionUID = 3634864186295639792L; - - private UserMenu userMenu = null; - private long lastDisposeTS = 0l; - - // - // MOUSE LISTENER - // - @Override - public void mouseDown(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (userMenu == null) { - long durationSinceLastDispose = System.currentTimeMillis() - - lastDisposeTS; - // avoid to reopen the menu, if one has clicked gain - if (durationSinceLastDispose > 200) { - userMenu = createUserMenu(source); - userMenu.getShell().addDisposeListener(this); - } - } - } - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void widgetDisposed(DisposeEvent event) { - userMenu = null; - lastDisposeTS = System.currentTimeMillis(); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java b/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java deleted file mode 100644 index 9ccc3057f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.argeo.cms.util.useradmin; - -import java.security.AccessController; -import java.util.List; -import java.util.Set; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; -import javax.security.auth.Subject; -import javax.security.auth.x500.X500Principal; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsView; -import org.argeo.cms.auth.AuthConstants; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.util.CmsUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.jcr.JcrUtils; -import org.argeo.osgi.useradmin.LdifName; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Centralise common patterns to manage roles with a user admin */ -public class UserAdminUtils { - - /** Retrieves a {@link Role} given a LDAP name */ - public final static Role getRole(UserAdmin userAdmin, LdapName dn) { - Role role = userAdmin.getRole(dn.toString()); - return role; - } - - /** Retrieves the unique local username given a {@link User}. */ - public final static String getUsername(User user) { - String username = null; - if (user instanceof Group) - username = getProperty(user, LdifName.cn.name()); - else - username = getProperty(user, LdifName.uid.name()); - return username; - } - - /** - * Easily retrieves one of the {@link Role}'s property or an empty String if - * the requested property is not defined - */ - public final static String getProperty(Role role, String key) { - Object obj = role.getProperties().get(key); - if (obj != null) - return (String) obj; - else - return ""; - } - - // CENTRALIZE SOME METHODS UNTIL API IS STABLE - /** Simply checks if current user is registered */ - public static boolean isRegistered() { - return !CurrentUser.isAnonymous(); - } - - /** Simply checks if current user as a home */ - public static boolean hasHome() { - return isRegistered(); - } - - // SELF HELPERS - /** Simply retrieves the current logged-in user display name. */ - public static User getCurrentUser(UserAdmin userAdmin) { - return (User) getRole(userAdmin, getCurrentUserLdapName()); - } - - /** Simply retrieves the current logged-in user display name. */ - public static String getCurrentUserDisplayName(UserAdmin userAdmin) { - String username = getCurrentUsername(); - return getUserDisplayName(userAdmin, username); - } - - /** Simply retrieves the current logged-in user display name. */ - public static String getCurrentUserMail(UserAdmin userAdmin) { - String username = getCurrentUsername(); - return getUserMail(userAdmin, username); - } - - /** Returns the local name of the current connected user */ - public final static String getUsername(UserAdmin userAdmin) { - LdapName dn = getCurrentUserLdapName(); - return getUsername((User) getRole(userAdmin, dn)); - } - - /** Returns true if the current user is in the specified role */ - public static boolean isUserInRole(String role) { - Set roles = CurrentUser.roles(); - return roles.contains(role); - } - - /** Simply checks if current user is the same as the passed one */ - public static boolean isCurrentUser(User user) { - String userName = getProperty(user, LdifName.dn.name()); - try { - LdapName selfUserName = getCurrentUserLdapName(); - LdapName userLdapName = new LdapName(userName); - if (userLdapName.equals(selfUserName)) - return true; - else - return false; - } catch (InvalidNameException e) { - throw new CmsException("User " + user + " has an unvalid dn: " - + userName, e); - } - } - - public final static LdapName getCurrentUserLdapName() { - String name = getCurrentUsername(); - return getLdapName(name); - } - - /** Simply retrieves username for current user, generally a LDAP dn */ - public static String getCurrentUsername() { - Subject subject = currentSubject(); - String name = subject.getPrincipals(X500Principal.class).iterator() - .next().toString(); - return name; - } - - /** - * Fork of the {@link CurrentUser#currentSubject} method that is private. - * TODO Enhance and factorize - */ - private static Subject currentSubject() { - CmsView cmsView = CmsUtils.getCmsView(); - if (cmsView != null) - return cmsView.getSubject(); - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject != null) - return subject; - throw new RuntimeException("Cannot find related subject"); - } - - // HOME MANAGEMENT - /** - * Simply retrieves the *relative* path to the current user home node from - * the base home node - */ - public static String getCurrentUserHomeRelPath() { - return getHomeRelPath(getCurrentUsername()); - } - - /** - * Simply retrieves the *relative* path to the home node of a user given its - * userName - */ - public static String getHomeRelPath(String userName) { - String id = getUserUid(userName); - String currHomePath = JcrUtils.firstCharsToPath(id, 2) + "/" + id; - return currHomePath; - } - - // HELPERS TO RETRIEVE REMARKABLE PROPERTIES - /** Simply retrieves the user uid from his dn with no useradmin */ - public static String getUserUid(String dn) { - LdapName ldapName = getLdapName(dn); - Rdn last = ldapName.getRdn(ldapName.size() - 1); - if (last.getType().toLowerCase().equals(LdifName.uid.name()) - || last.getType().toLowerCase().equals(LdifName.cn.name())) - return (String) last.getValue(); - else - throw new CmsException("Cannot retrieve user uid, " - + "non valid dn: " + dn); - } - - /** - * Returns the local username if no user with this dn is found or if the - * found user has no defined display name - */ - public static String getUserDisplayName(UserAdmin userAdmin, String dn) { - Role user = getRole(userAdmin, getLdapName(dn)); - if (user == null) - return getUserUid(dn); - String displayName = getProperty(user, LdifName.displayName.name()); - if (EclipseUiUtils.isEmpty(displayName)) - displayName = getProperty(user, LdifName.cn.name()); - if (EclipseUiUtils.isEmpty(displayName)) - return getUserUid(dn); - else - return displayName; - } - - /** - * Returns null if no user with this dn is found or if the found user has no - * defined mail - */ - public static String getUserMail(UserAdmin userAdmin, String dn) { - Role user = getRole(userAdmin, getLdapName(dn)); - if (user == null) - return null; - else - return getProperty(user, LdifName.mail.name()); - } - - // VARIOUS UI HELPERS - public final static String buildDefaultCn(String firstName, String lastName) { - return (firstName.trim() + " " + lastName.trim() + " ").trim(); - } - - /** Simply retrieves a display name of the relevant domain */ - public final static String getDomainName(User user) { - String dn = user.getName(); - if (dn.endsWith(AuthConstants.ROLES_BASEDN)) - return "System roles"; - try { - LdapName name = new LdapName(dn); - List rdns = name.getRdns(); - String dname = null; - int i = 0; - loop: while (i < rdns.size()) { - Rdn currrRdn = rdns.get(i); - if (!LdifName.dc.name().equals(currrRdn.getType())) - break loop; - else { - String currVal = (String) currrRdn.getValue(); - dname = dname == null ? currVal : currVal + "." + dname; - } - i++; - } - return dname; - } catch (InvalidNameException e) { - throw new CmsException("Unable to get domain name for " + dn, e); - } - } - - // Local Helpers - /** Simply retrieves a LDAP name from a dn with no exception */ - public static LdapName getLdapName(String dn) { - try { - return new LdapName(dn); - } catch (InvalidNameException e) { - throw new CmsException("Cannot parse LDAP name " + dn, e); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java b/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java deleted file mode 100644 index aa764d57c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.argeo.cms.util.useradmin; - -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.transaction.Status; -import javax.transaction.UserTransaction; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.AuthConstants; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.osgi.framework.ServiceReference; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** - * Base useradmin wrapper. Implementing application might extends to add - * business specific behaviour - */ -public abstract class UserAdminWrapper { - // private Log log = LogFactory.getLog(UserAdminWrapper.class); - - private UserAdmin userAdmin; - private ServiceReference userAdminServiceReference; - private UserTransaction userTransaction; - - /* USER ADMIN LISTENER MANAGEMENT */ - List listeners = new ArrayList(); - - // TODO implement safer mechanism - public void addListener(UserAdminListener userAdminListener) { - if (!listeners.contains(userAdminListener)) - listeners.add(userAdminListener); - } - - /** - * Starts a transaction if none already exists and notify the userAdmin - * listeners.Must be called from the UI Thread. - */ - public UserTransaction beginTransactionIfNeeded() { - try { - if (userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION) { - userTransaction.begin(); - } - return userTransaction; - } catch (Exception e) { - throw new CmsException("Unable to begin transaction", e); - } - } - - // Expose this? - public void removeListener(UserAdminListener userAdminListener) { - if (listeners.contains(userAdminListener)) - listeners.remove(userAdminListener); - } - - public void notifyListeners(UserAdminEvent event) { - for (UserAdminListener listener : listeners) - listener.roleChanged(event); - } - - public Map getKnownBaseDns(boolean onlyWritable) { - Map dns = new HashMap(); - for (String uri : userAdminServiceReference.getPropertyKeys()) { - if (!uri.startsWith("/")) - continue; - Dictionary props = UserAdminConf.uriAsProperties(uri); - String readOnly = UserAdminConf.readOnly.getValue(props); - String baseDn = UserAdminConf.baseDn.getValue(props); - - if (onlyWritable && "true".equals(readOnly)) - continue; - if (baseDn.equalsIgnoreCase(AuthConstants.ROLES_BASEDN)) - continue; - dns.put(baseDn, uri); - } - return dns; - } - - public UserAdmin getUserAdmin() { - return userAdmin; - } - - public UserTransaction getUserTransaction() { - return userTransaction; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdmin(UserAdmin userAdmin) { - this.userAdmin = userAdmin; - } - - public void setUserTransaction(UserTransaction userTransaction) { - this.userTransaction = userTransaction; - } - - public void setUserAdminServiceReference( - ServiceReference userAdminServiceReference) { - this.userAdminServiceReference = userAdminServiceReference; - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java b/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java deleted file mode 100644 index b52f76be5..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java +++ /dev/null @@ -1,309 +0,0 @@ -package org.argeo.cms.viewers; - -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsException; -import org.argeo.cms.widgets.ScrolledPage; -import org.eclipse.jface.viewers.ContentViewer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Widget; -import org.xml.sax.SAXParseException; - -/** Base class for viewers related to a page */ -public abstract class AbstractPageViewer extends ContentViewer implements - Observer { - private static final long serialVersionUID = 5438688173410341485L; - - private final static Log log = LogFactory.getLog(AbstractPageViewer.class); - - private final boolean readOnly; - /** The basis for the layouts, typically a ScrolledPage. */ - private final Composite page; - private final CmsEditable cmsEditable; - - private MouseListener mouseListener; - private FocusListener focusListener; - - private EditablePart edited; - private ISelection selection = StructuredSelection.EMPTY; - - protected AbstractPageViewer(Section parent, int style, - CmsEditable cmsEditable) { - // read only at UI level - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - - this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE - : cmsEditable; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - - if (cmsEditable.canEdit()) { - mouseListener = createMouseListener(); - focusListener = createFocusListener(); - } - page = findPage(parent); - } - - /** - * Can be called to simplify the called to isModelInitialized() and - * initModel() - */ - protected void initModelIfNeeded(Node node) { - try { - if (!isModelInitialized(node)) - if (getCmsEditable().canEdit()) { - initModel(node); - node.getSession().save(); - } - } catch (Exception e) { - throw new CmsException("Cannot initialize model", e); - } - } - - /** Called if user can edit and model is not initialized */ - protected Boolean isModelInitialized(Node node) throws RepositoryException { - return true; - } - - /** Called if user can edit and model is not initialized */ - protected void initModel(Node node) throws RepositoryException { - } - - /** Create (retrieve) the MouseListener to use. */ - protected MouseListener createMouseListener() { - return new MouseAdapter() { - private static final long serialVersionUID = 1L; - }; - } - - /** Create (retrieve) the FocusListener to use. */ - protected FocusListener createFocusListener() { - return new FocusListener() { - private static final long serialVersionUID = 1L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - } - }; - } - - protected Composite findPage(Composite composite) { - if (composite instanceof ScrolledPage) { - return (ScrolledPage) composite; - } else { - if (composite.getParent() == null) - return composite; - return findPage(composite.getParent()); - } - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) - editingStateChanged(cmsEditable); - } - - /** To be overridden in order to provide the actual refresh */ - protected void refresh(Control control) throws RepositoryException { - } - - /** To be overridden.Save the edited part. */ - protected void save(EditablePart part) throws RepositoryException { - } - - /** Prepare the edited part */ - protected void prepare(EditablePart part, Object caretPosition) { - } - - /** Notified when the editing state changed. Does nothing, to be overridden */ - protected void editingStateChanged(CmsEditable cmsEditable) { - } - - @Override - public void refresh() { - try { - if (cmsEditable.canEdit() && !readOnly) - mouseListener = createMouseListener(); - else - mouseListener = null; - refresh(getControl()); - layout(getControl()); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh", e); - } - } - - @Override - public void setSelection(ISelection selection, boolean reveal) { - this.selection = selection; - } - - protected void updateContent(EditablePart part) throws RepositoryException { - } - - // LOW LEVEL EDITION - protected void edit(EditablePart part, Object caretPosition) { - try { - if (edited == part) - return; - - if (edited != null && edited != part) { - EditablePart previouslyEdited = edited; - try { - stopEditing(true); - } catch (Exception e) { - notifyEditionException(e); - edit(previouslyEdited, caretPosition); - return; - } - } - - part.startEditing(); - updateContent(part); - prepare(part, caretPosition); - edited = part; - layout(part.getControl()); - } catch (RepositoryException e) { - throw new CmsException("Cannot edit " + part, e); - } - } - - private void stopEditing(Boolean save) throws RepositoryException { - if (edited instanceof Widget && ((Widget) edited).isDisposed()) { - edited = null; - return; - } - - assert edited != null; - if (edited == null) { - if (log.isTraceEnabled()) - log.warn("Told to stop editing while not editing anything"); - return; - } - - if (save) - save(edited); - - edited.stopEditing(); - updateContent(edited); - layout(((EditablePart) edited).getControl()); - edited = null; - } - - // METHODS AVAILABLE TO EXTENDING CLASSES - protected void saveEdit() { - try { - if (edited != null) - stopEditing(true); - } catch (RepositoryException e) { - throw new CmsException("Cannot stop editing", e); - } - } - - protected void cancelEdit() { - try { - if (edited != null) - stopEditing(false); - } catch (RepositoryException e) { - throw new CmsException("Cannot cancel editing", e); - } - } - - /** Layout this controls from the related base page. */ - public void layout(Control... controls) { - page.layout(controls); - } - - /** - * Find the first {@link EditablePart} in the parents hierarchy of this - * control - */ - protected EditablePart findDataParent(Control parent) { - if (parent instanceof EditablePart) { - return (EditablePart) parent; - } - if (parent.getParent() != null) - return findDataParent(parent.getParent()); - else - throw new CmsException("No data parent found"); - } - - // UTILITIES - /** Check whether the edited part is in a proper state */ - protected void checkEdited() { - if (edited == null || (edited instanceof Widget) - && ((Widget) edited).isDisposed()) - throw new CmsException( - "Edited should not be null or disposed at this stage"); - } - - /** Persist all changes. */ - protected void persistChanges(Session session) throws RepositoryException { - session.save(); - session.refresh(false); - // TODO notify that changes have been persisted - } - - /** Convenience method using a Node in order to save the underlying session. */ - protected void persistChanges(Node anyNode) throws RepositoryException { - persistChanges(anyNode.getSession()); - } - - /** Notify edition exception */ - protected void notifyEditionException(Throwable e) { - Throwable eToLog = e; - if (e instanceof IllegalArgumentException) - if (e.getCause() instanceof SAXParseException) - eToLog = e.getCause(); - log.error(eToLog.getMessage()); - if (log.isTraceEnabled()) - log.trace("Full stack of " + eToLog.getMessage(), e); - // TODO Light error notification popup - } - - // GETTERS / SETTERS - public boolean isReadOnly() { - return readOnly; - } - - protected EditablePart getEdited() { - return edited; - } - - public MouseListener getMouseListener() { - return mouseListener; - } - - public FocusListener getFocusListener() { - return focusListener; - } - - public CmsEditable getCmsEditable() { - return cmsEditable; - } - - @Override - public ISelection getSelection() { - return selection; - } -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/EditablePart.java b/org.argeo.cms/src/org/argeo/cms/viewers/EditablePart.java deleted file mode 100644 index 99f8acf9a..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/EditablePart.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.viewers; - -import org.eclipse.swt.widgets.Control; - -public interface EditablePart { - public void startEditing(); - - public void stopEditing(); - - public Control getControl(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/ItemPart.java b/org.argeo.cms/src/org/argeo/cms/viewers/ItemPart.java deleted file mode 100644 index 52e5a88eb..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/ItemPart.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.viewers; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -/** An editable part related to a JCR Item */ -public interface ItemPart { - public Item getItem() throws RepositoryException; -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java b/org.argeo.cms/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java deleted file mode 100644 index 2d7daae59..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/JcrVersionCmsEditable.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.argeo.cms.viewers; - -import java.util.Observable; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.version.VersionManager; - -import org.argeo.cms.CmsEditable; -import org.argeo.cms.CmsEditionEvent; -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; - -/** Provides the CmsEditable semantic based on JCR versioning. */ -public class JcrVersionCmsEditable extends Observable implements CmsEditable { - private final String nodePath;// cache - private final VersionManager versionManager; - private final Boolean canEdit; - - public JcrVersionCmsEditable(Node node) throws RepositoryException { - this.nodePath = node.getPath(); - if (node.getSession().hasPermission(node.getPath(), - Session.ACTION_SET_PROPERTY)) { - // was Session.ACTION_ADD_NODE - canEdit = true; - if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) { - node.addMixin(NodeType.MIX_VERSIONABLE); - node.getSession().save(); - } - versionManager = node.getSession().getWorkspace() - .getVersionManager(); - } else { - canEdit = false; - versionManager = null; - } - - // bind keys - if (canEdit) { - Display display = Display.getCurrent(); - display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN", - "CTRL+E" }); - display.addFilter(SWT.KeyDown, new Listener() { - private static final long serialVersionUID = -4378653870463187318L; - - public void handleEvent(Event e) { - boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0; - if (ctrlPressed && e.keyCode == '\r') - stopEditing(); - else if (ctrlPressed && e.keyCode == 'E') - stopEditing(); - } - }); - } - } - - @Override - public Boolean canEdit() { - return canEdit; - } - - public Boolean isEditing() { - try { - if (!canEdit()) - return false; - return versionManager.isCheckedOut(nodePath); - } catch (RepositoryException e) { - throw new CmsException("Cannot check whether " + nodePath - + " is editing", e); - } - } - - @Override - public void startEditing() { - try { - versionManager.checkout(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.START_EDITING)); - } - - @Override - public void stopEditing() { - try { - versionManager.checkin(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath, e1); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.STOP_EDITING)); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/NodePart.java b/org.argeo.cms/src/org/argeo/cms/viewers/NodePart.java deleted file mode 100644 index db9a60a87..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/NodePart.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.viewers; - -import javax.jcr.Node; - -/** An editable part related to a node */ -public interface NodePart extends ItemPart { - public Node getNode(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/PropertyPart.java b/org.argeo.cms/src/org/argeo/cms/viewers/PropertyPart.java deleted file mode 100644 index 50fdd0601..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/PropertyPart.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.viewers; - -import javax.jcr.Property; - -/** An editable part related to a JCR Property */ -public interface PropertyPart extends ItemPart { - public Property getProperty(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/Section.java b/org.argeo.cms/src/org/argeo/cms/viewers/Section.java deleted file mode 100644 index af7fd877f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/Section.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.argeo.cms.viewers; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.CmsNames; -import org.argeo.cms.util.CmsUtils; -import org.argeo.cms.widgets.JcrComposite; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -public class Section extends JcrComposite implements CmsNames { - private static final long serialVersionUID = -5933796173755739207L; - - private final Section parentSection; - private Composite sectionHeader; - private final Integer relativeDepth; - - public Section(Composite parent, int style, Node node) - throws RepositoryException { - this(parent, findSection(parent), style, node); - } - - public Section(Section section, int style, Node node) - throws RepositoryException { - this(section, section, style, node); - } - - protected Section(Composite parent, Section parentSection, int style, - Node node) throws RepositoryException { - super(parent, style, node); - this.parentSection = parentSection; - if (parentSection != null) { - relativeDepth = getNode().getDepth() - - parentSection.getNode().getDepth(); - } else { - relativeDepth = 0; - } - setLayout(CmsUtils.noSpaceGridLayout()); - } - - public Map getSubSections() throws RepositoryException { - LinkedHashMap result = new LinkedHashMap(); - for (Control child : getChildren()) { - if (child instanceof Composite) { - collectDirectSubSections((Composite) child, result); - } - } - return Collections.unmodifiableMap(result); - } - - private void collectDirectSubSections(Composite composite, - LinkedHashMap subSections) - throws RepositoryException { - if (composite == sectionHeader || composite instanceof EditablePart) - return; - if (composite instanceof Section) { - Section section = (Section) composite; - subSections.put(section.getNodeId(), section); - return; - } - - for (Control child : composite.getChildren()) - if (child instanceof Composite) - collectDirectSubSections((Composite) child, subSections); - } - - public void createHeader() { - if (sectionHeader != null) - throw new CmsException("Section header was already created"); - - sectionHeader = new Composite(this, SWT.NONE); - sectionHeader.setLayoutData(CmsUtils.fillWidth()); - sectionHeader.setLayout(CmsUtils.noSpaceGridLayout()); - // sectionHeader.moveAbove(null); - // layout(); - } - - public Composite getHeader() { - if (sectionHeader != null && sectionHeader.isDisposed()) - sectionHeader = null; - return sectionHeader; - } - - // SECTION PARTS - public SectionPart getSectionPart(String partId) { - for (Control child : getChildren()) { - if (child instanceof SectionPart) { - SectionPart paragraph = (SectionPart) child; - if (paragraph.getPartId().equals(partId)) - return paragraph; - } - } - return null; - } - - public SectionPart nextSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) - if (i + 1 < children.length) { - Composite next = (Composite) children[i + 1]; - return (SectionPart) next; - } else { - // next section - } - } - return null; - } - - public SectionPart previousSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) - if (i != 0) { - Composite previous = (Composite) children[i - 1]; - return (SectionPart) previous; - } else { - // previous section - } - } - return null; - } - - @Override - public String toString() { - if (parentSection == null) - return "Main section " + getNode(); - return "Section " + getNode(); - } - - public Section getParentSection() { - return parentSection; - } - - public Integer getRelativeDepth() { - return relativeDepth; - } - - /** Recursively finds the related section in the parents (can be itself) */ - public static Section findSection(Control control) { - if (control == null) - return null; - if (control instanceof Section) - return (Section) control; - else - return findSection(control.getParent()); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/SectionPart.java b/org.argeo.cms/src/org/argeo/cms/viewers/SectionPart.java deleted file mode 100644 index 6cd45c5ba..000000000 --- a/org.argeo.cms/src/org/argeo/cms/viewers/SectionPart.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.viewers; - - -/** An editable part dynamically related to a Section */ -public interface SectionPart extends EditablePart, NodePart { - public String getPartId(); - - public Section getSection(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/EditableImage.java b/org.argeo.cms/src/org/argeo/cms/widgets/EditableImage.java deleted file mode 100644 index 2e70eb89f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/EditableImage.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.argeo.cms.widgets; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** A stylable and editable image. */ -public abstract class EditableImage extends StyledControl { - private static final long serialVersionUID = -5689145523114022890L; - private final static Log log = LogFactory.getLog(EditableImage.class); - - private Point preferredImageSize; - private Boolean loaded = false; - - public EditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - public EditableImage(Composite parent, int swtStyle, - Point preferredImageSize) { - super(parent, swtStyle); - this.preferredImageSize = preferredImageSize; - } - - public EditableImage(Composite parent, int style, Node node, - boolean cacheImmediately, Point preferredImageSize) - throws RepositoryException { - super(parent, style, node, cacheImmediately); - this.preferredImageSize = preferredImageSize; - } - - @Override - protected void setContainerLayoutData(Composite composite) { - // composite.setLayoutData(fillWidth()); - } - - @Override - protected void setControlLayoutData(Control control) { - // control.setLayoutData(fillWidth()); - } - - /** To be overriden. */ - protected String createImgTag() throws RepositoryException { - return CmsUtils.noImg(preferredImageSize != null ? preferredImageSize - : getSize()); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle()); - // lbl.setLayoutData(CmsUtils.fillWidth()); - CmsUtils.markup(lbl); - CmsUtils.style(lbl, style); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - load(lbl); - return lbl; - } - - /** To be overriden. */ - protected synchronized Boolean load(Control control) { - String imgTag; - try { - imgTag = createImgTag(); - } catch (Exception e) { - // throw new CmsException("Cannot retrieve image", e); - log.error("Cannot retrieve image", e); - imgTag = CmsUtils.noImg(preferredImageSize); - loaded = false; - } - - if (imgTag == null) { - loaded = false; - imgTag = CmsUtils.noImg(preferredImageSize); - } else - loaded = true; - if (control != null) { - ((Label) control).setText(imgTag); - control.setSize(preferredImageSize != null ? preferredImageSize - : getSize()); - } else { - loaded = false; - } - getParent().layout(); - return loaded; - } - - public void setPreferredSize(Point size) { - this.preferredImageSize = size; - if (!loaded) { - load((Label) getControl()); - } - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsUtils.style(text, style); - return text; - } - - public Point getPreferredImageSize() { - return preferredImageSize; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/EditableText.java b/org.argeo.cms/src/org/argeo/cms/widgets/EditableText.java deleted file mode 100644 index e7c56ea72..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/EditableText.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.argeo.cms.widgets; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable text part displaying styled text. */ -public class EditableText extends StyledControl { - private static final long serialVersionUID = -6372283442330912755L; - - public EditableText(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - public EditableText(Composite parent, int style, Item item) - throws RepositoryException { - this(parent, style, item, false); - } - - public EditableText(Composite parent, int style, Item item, - boolean cacheImmediately) throws RepositoryException { - super(parent, style, item, cacheImmediately); - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) - return createText(box, style); - else - return createLabel(box, style); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsUtils.fillWidth()); - CmsUtils.style(lbl, style); - CmsUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - protected Text createText(Composite box, String style) { - final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP); - GridData textLayoutData = CmsUtils.fillWidth(); - // textLayoutData.heightHint = preferredHeight; - text.setLayoutData(textLayoutData); - CmsUtils.style(text, style); - text.setFocus(); - return text; - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) - ((Label) child).setText(text); - else if (child instanceof Text) - ((Text) child).setText(text); - } - - public Text getAsText() { - return (Text) getControl(); - } - - public Label getAsLabel() { - return (Label) getControl(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/JcrComposite.java b/org.argeo.cms/src/org/argeo/cms/widgets/JcrComposite.java deleted file mode 100644 index 358b45315..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/JcrComposite.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.argeo.cms.widgets; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.cms.CmsException; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; - -/** A composite which can (optionally) manage a JCR Item. */ -public class JcrComposite extends Composite { - private static final long serialVersionUID = -1447009015451153367L; - - private final Session session; - - private String nodeId; - private String property = null; - private Node cache; - - /** Regular composite constructor. No layout is set. */ - public JcrComposite(Composite parent, int style) { - super(parent, style); - session = null; - nodeId = null; - } - - public JcrComposite(Composite parent, int style, Item item) - throws RepositoryException { - this(parent, style, item, false); - } - - public JcrComposite(Composite parent, int style, Item item, - boolean cacheImmediately) throws RepositoryException { - super(parent, style); - this.session = item.getSession(); - if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) { - // (useless?) optimization: we only save a pointer to the session, - // not even a reference to the item - this.nodeId = null; - } else { - Node node; - Property property = null; - if (item instanceof Node) { - node = (Node) item; - } else {// Property - property = (Property) item; - if (property.isMultiple())// TODO manage property index - throw new CmsException( - "Multiple properties not supported yet."); - this.property = property.getName(); - node = property.getParent(); - } - this.nodeId = node.getIdentifier(); - if (cacheImmediately) - this.cache = node; - } - setLayout(CmsUtils.noSpaceGridLayout()); - } - - public synchronized Node getNode() { - try { - if (!itemIsNode()) - throw new CmsException("Item is not a Node"); - return getNodeInternal(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get node " + nodeId, e); - } - } - - private synchronized Node getNodeInternal() throws RepositoryException { - if (cache != null) - return cache; - else if (session != null) - if (nodeId != null) - return session.getNodeByIdentifier(nodeId); - else - return null; - else - return null; - } - - public synchronized Property getProperty() { - try { - if (itemIsNode()) - throw new CmsException("Item is not a Property"); - Node node = getNodeInternal(); - if (!node.hasProperty(property)) - throw new CmsException("Property " + property - + " is not set on " + node); - return node.getProperty(property); - } catch (RepositoryException e) { - throw new CmsException("Cannot get property " + property - + " from node " + nodeId, e); - } - } - - public synchronized Boolean itemIsNode() { - return property == null; - } - - /** Set/update the cache or change the node */ - public synchronized void setNode(Node node) throws RepositoryException { - if (!itemIsNode()) - throw new CmsException("Cannot set a Node on a Property"); - - if (node == null) {// clear cache - this.cache = null; - return; - } - - if (session == null || session != node.getSession())// check session - throw new CmsException("Uncompatible session"); - - if (nodeId == null || !nodeId.equals(node.getIdentifier())) { - nodeId = node.getIdentifier(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } - - /** Set/update the cache or change the property */ - public synchronized void setProperty(Property prop) - throws RepositoryException { - if (itemIsNode()) - throw new CmsException("Cannot set a Property on a Node"); - - if (prop == null) {// clear cache - this.cache = null; - return; - } - - if (session == null || session != prop.getSession())// check session - throw new CmsException("Uncompatible session"); - - Node node = prop.getNode(); - if (nodeId == null || !nodeId.equals(node.getIdentifier()) - || !property.equals(prop.getName())) { - nodeId = node.getIdentifier(); - property = prop.getName(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } - - public synchronized String getNodeId() { - return nodeId; - } - - /** Change the node, does nothing if same. */ - public synchronized void setNodeId(String nodeId) - throws RepositoryException { - if (this.nodeId != null && this.nodeId.equals(nodeId)) - return; - this.nodeId = nodeId; - if (cache != null) - cache = session.getNodeByIdentifier(this.nodeId); - itemUpdated(); - } - - protected synchronized void itemUpdated() { - layout(); - } - - public Session getSession() { - return session; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/ScrolledPage.java b/org.argeo.cms/src/org/argeo/cms/widgets/ScrolledPage.java deleted file mode 100644 index c36ed2052..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/ScrolledPage.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.cms.widgets; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Composite; - -/** - * A composite that can be scrolled vertically. It wraps a - * {@link ScrolledComposite} (and is being wrapped by it), simplifying its - * configuration. - */ -public class ScrolledPage extends Composite { - private static final long serialVersionUID = 1593536965663574437L; - - private ScrolledComposite scrolledComposite; - - public ScrolledPage(Composite parent, int style) { - super(new ScrolledComposite(parent, SWT.V_SCROLL), style); - scrolledComposite = (ScrolledComposite) getParent(); - scrolledComposite.setContent(this); - - scrolledComposite.setExpandVertical(true); - scrolledComposite.setExpandHorizontal(true); - scrolledComposite.addControlListener(new ScrollControlListener()); - } - - @Override - public void layout(boolean changed, boolean all) { - updateScroll(); - super.layout(changed, all); - } - - protected void updateScroll() { - Rectangle r = scrolledComposite.getClientArea(); - Point preferredSize = computeSize(r.width, SWT.DEFAULT); - scrolledComposite.setMinHeight(preferredSize.y); - } - - // public ScrolledComposite getScrolledComposite() { - // return this.scrolledComposite; - // } - - /** Set it on the wrapping scrolled composite */ - @Override - public void setLayoutData(Object layoutData) { - scrolledComposite.setLayoutData(layoutData); - } - - private class ScrollControlListener extends - org.eclipse.swt.events.ControlAdapter { - private static final long serialVersionUID = -3586986238567483316L; - - public void controlResized(ControlEvent e) { - updateScroll(); - } - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/StyledControl.java b/org.argeo.cms/src/org/argeo/cms/widgets/StyledControl.java deleted file mode 100644 index ec1dbe77e..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/StyledControl.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.cms.widgets; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsConstants; -import org.argeo.cms.CmsNames; -import org.argeo.cms.util.CmsUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Editable text part displaying styled text. */ -public abstract class StyledControl extends JcrComposite implements - CmsConstants, CmsNames { - private static final long serialVersionUID = -6372283442330912755L; - private Control control; - - private Composite container; - private Composite box; - - protected MouseListener mouseListener; - protected FocusListener focusListener; - - private Boolean editing = Boolean.FALSE; - - public StyledControl(Composite parent, int swtStyle) { - super(parent, swtStyle); - setLayout(CmsUtils.noSpaceGridLayout()); - } - - public StyledControl(Composite parent, int style, Item item) - throws RepositoryException { - super(parent, style, item); - } - - public StyledControl(Composite parent, int style, Item item, - boolean cacheImmediately) throws RepositoryException { - super(parent, style, item, cacheImmediately); - } - - protected abstract Control createControl(Composite box, String style); - - protected Composite createBox(Composite parent) { - Composite box = new Composite(parent, SWT.INHERIT_DEFAULT); - setContainerLayoutData(box); - box.setLayout(CmsUtils.noSpaceGridLayout()); - // new Label(box, SWT.NONE).setText("BOX"); - return box; - } - - public Control getControl() { - return control; - } - - protected synchronized Boolean isEditing() { - return editing; - } - - public synchronized void startEditing() { - assert !isEditing(); - editing = true; - // int height = control.getSize().y; - String style = (String) control.getData(STYLE); - clear(false); - control = createControl(box, style); - setControlLayoutData(control); - - // add the focus listener to the newly created edition control - if (focusListener != null) - control.addFocusListener(focusListener); - } - - public synchronized void stopEditing() { - assert isEditing(); - editing = false; - String style = (String) control.getData(STYLE); - clear(false); - control = createControl(box, style); - setControlLayoutData(control); - } - - public void setStyle(String style) { - Object currentStyle = null; - if (control != null) - currentStyle = control.getData(STYLE); - if (currentStyle != null && currentStyle.equals(style)) - return; - - // Integer preferredHeight = control != null ? control.getSize().y : - // null; - clear(true); - control = createControl(box, style); - setControlLayoutData(control); - - control.getParent().setData(STYLE, style + "_box"); - control.getParent().getParent().setData(STYLE, style + "_container"); - } - - /** To be overridden */ - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsUtils.fillWidth()); - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsUtils.fillWidth()); - } - - protected void clear(boolean deep) { - if (deep) { - for (Control control : getChildren()) - control.dispose(); - container = createBox(this); - box = createBox(container); - } else { - control.dispose(); - } - } - - public void setMouseListener(MouseListener mouseListener) { - if (this.mouseListener != null && control != null) - control.removeMouseListener(this.mouseListener); - this.mouseListener = mouseListener; - if (control != null && this.mouseListener != null) - control.addMouseListener(mouseListener); - } - - public void setFocusListener(FocusListener focusListener) { - if (this.focusListener != null && control != null) - control.removeFocusListener(this.focusListener); - this.focusListener = focusListener; - if (control != null && this.focusListener != null) - control.addFocusListener(focusListener); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java deleted file mode 100644 index b86fcb0b0..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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. - */ -package org.argeo.cms.widgets.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.operation.ModalContext; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.osgi.framework.FrameworkUtil; - -/** Base for login dialogs */ -public abstract class AbstractLoginDialog extends TrayDialog implements CallbackHandler { - private static final long serialVersionUID = -8046708963512717709L; - - private final static Log log = LogFactory.getLog(AbstractLoginDialog.class); - - private Thread modalContextThread = null; - boolean processCallbacks = false; - boolean isCancelled = false; - Callback[] callbackArray; - - protected final Callback[] getCallbacks() { - return this.callbackArray; - } - - public abstract void internalHandle(); - - public boolean isCancelled() { - return isCancelled; - } - - protected AbstractLoginDialog(Shell parentShell) { - super(parentShell); - } - - /* - * (non-Javadoc) - * - * @see - * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth - * .callback.Callback[]) - */ - public void handle(final Callback[] callbacks) throws IOException { - // clean previous usage - if (processCallbacks) { - // this handler was already used - processCallbacks = false; - } - - if (modalContextThread != null) { - try { - modalContextThread.join(1000); - } catch (InterruptedException e) { - // silent - } - modalContextThread = null; - } - - // initialize - this.callbackArray = callbacks; - final Display display = Display.getDefault(); - display.syncExec(new Runnable() { - - public void run() { - isCancelled = false; - setBlockOnOpen(false); - open(); - - final Button okButton = getButton(IDialogConstants.OK_ID); - okButton.setText("Login"); - okButton.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = -200281625679096775L; - - public void widgetSelected(final SelectionEvent event) { - processCallbacks = true; - } - - public void widgetDefaultSelected(final SelectionEvent event) { - // nothing to do - } - }); - final Button cancel = getButton(IDialogConstants.CANCEL_ID); - cancel.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = -3826030278084915815L; - - public void widgetSelected(final SelectionEvent event) { - isCancelled = true; - processCallbacks = true; - } - - public void widgetDefaultSelected(final SelectionEvent event) { - // nothing to do - } - }); - } - }); - try { - ModalContext.setAllowReadAndDispatch(true); // Works for now. - ModalContext.run(new IRunnableWithProgress() { - - public void run(final IProgressMonitor monitor) { - modalContextThread = Thread.currentThread(); - // Wait here until OK or cancel is pressed, then let it rip. - // The event - // listener - // is responsible for closing the dialog (in the - // loginSucceeded - // event). - while (!processCallbacks && (modalContextThread != null) - && (modalContextThread == Thread.currentThread()) - && FrameworkUtil.getBundle(AbstractLoginDialog.class).getBundleContext() != null) { - // Note: SecurityUiPlugin.getDefault() != null is false - // when the OSGi runtime is shut down - try { - Thread.sleep(100); - // if (display.isDisposed()) { - // log.warn("Display is disposed, killing login - // dialog thread"); - // throw new ThreadDeath(); - // } - } catch (final Exception e) { - // do nothing - } - } - processCallbacks = false; - // Call the adapter to handle the callbacks - if (!isCancelled()) - internalHandle(); - else - // clear callbacks are when cancelling - for (Callback callback : callbacks) - if (callback instanceof PasswordCallback) { - char[] arr = ((PasswordCallback) callback).getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - ((PasswordCallback) callback).setPassword(null); - } - } else if (callback instanceof NameCallback) - ((NameCallback) callback).setName(null); - } - }, true, new NullProgressMonitor(), Display.getDefault()); - } catch (ThreadDeath e) { - isCancelled = true; - log.debug("Thread " + Thread.currentThread().getId() + " died"); - throw e; - } catch (Exception e) { - isCancelled = true; - IOException ioe = new IOException("Unexpected issue in login dialog, see root cause for more details"); - ioe.initCause(e); - throw ioe; - } finally { - // so that the modal thread dies - processCallbacks = true; - // try { - // // wait for the modal context thread to gracefully exit - // modalContextThread.join(); - // } catch (InterruptedException ie) { - // // silent - // } - modalContextThread = null; - } - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText("Authentication"); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java deleted file mode 100644 index 8f00c4577..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java +++ /dev/null @@ -1,308 +0,0 @@ -package org.argeo.cms.widgets.auth; - -import static org.argeo.cms.CmsMsg.password; -import static org.argeo.cms.CmsMsg.username; -import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_ANONYMOUS; -import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_USER; -import static org.argeo.cms.internal.kernel.Activator.getNodeState; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.LanguageCallback; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.FailedLoginException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.CmsStyles; -import org.argeo.cms.CmsView; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.HttpRequestCallback; -import org.argeo.cms.i18n.LocaleUtils; -import org.argeo.cms.internal.auth.LocaleChoice; -import org.argeo.cms.util.CmsUtils; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -public class CmsLogin implements CmsStyles, CallbackHandler { - private final static Log log = LogFactory.getLog(CmsLogin.class); - - private Composite parent; - private Text usernameT, passwordT; - private Composite credentialsBlock; - private final SelectionListener loginSelectionListener; - - private final Locale defaultLocale; - private LocaleChoice localeChoice = null; - - private final CmsView cmsView; - - public CmsLogin(CmsView cmsView) { - this.cmsView = cmsView; - defaultLocale = getNodeState().getDefaultLocale(); - List locales = getNodeState().getLocales(); - if (locales != null) - localeChoice = new LocaleChoice(locales, defaultLocale); - loginSelectionListener = new SelectionListener() { - private static final long serialVersionUID = -8832133363830973578L; - - @Override - public void widgetSelected(SelectionEvent e) { - login(); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }; - } - - protected boolean isAnonymous() { - return CurrentUser.isAnonymous(cmsView.getSubject()); - } - - public final void createUi(Composite parent) { - this.parent = parent; - createContents(parent); - } - - protected void createContents(Composite parent) { - defaultCreateContents(parent); - } - - public final void defaultCreateContents(Composite parent) { - parent.setLayout(CmsUtils.noSpaceGridLayout()); - Composite credentialsBlock = createCredentialsBlock(parent); - if (parent instanceof Shell) { - credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, - true, true)); - } - } - - public final Composite createCredentialsBlock(Composite parent) { - if (isAnonymous()) { - return anonymousUi(parent); - } else { - return userUi(parent); - } - } - - protected Composite getCredentialsBlock() { - return credentialsBlock; - } - - protected Composite userUi(Composite parent) { - Locale locale = localeChoice == null ? this.defaultLocale - : localeChoice.getSelectedLocale(); - credentialsBlock = new Composite(parent, SWT.NONE); - credentialsBlock.setLayout(new GridLayout()); - credentialsBlock.setLayoutData(CmsUtils.fillAll()); - - specificUserUi(credentialsBlock); - - Label l = new Label(credentialsBlock, SWT.NONE); - l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM); - l.setText(CmsMsg.logout.lead(locale)); - GridData lData = CmsUtils.fillWidth(); - lData.widthHint = 120; - l.setLayoutData(lData); - - l.addMouseListener(new MouseAdapter() { - private static final long serialVersionUID = 6444395812777413116L; - - public void mouseDown(MouseEvent e) { - logout(); - } - }); - return credentialsBlock; - } - - /** To be overridden */ - protected void specificUserUi(Composite parent) { - - } - - protected Composite anonymousUi(Composite parent) { - Locale locale = localeChoice == null ? this.defaultLocale - : localeChoice.getSelectedLocale(); - // We need a composite for the traversal - credentialsBlock = new Composite(parent, SWT.NONE); - credentialsBlock.setLayout(new GridLayout()); - credentialsBlock.setLayoutData(CmsUtils.fillAll()); - - Integer textWidth = 120; - parent.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - - // new Label(this, SWT.NONE).setText(CmsMsg.username.lead()); - usernameT = new Text(credentialsBlock, SWT.BORDER); - usernameT.setMessage(username.lead(locale)); - usernameT.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_USERNAME); - GridData gd = CmsUtils.fillWidth(); - gd.widthHint = textWidth; - usernameT.setLayoutData(gd); - - // new Label(this, SWT.NONE).setText(CmsMsg.password.lead()); - passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD); - passwordT.setMessage(password.lead(locale)); - passwordT.setData(RWT.CUSTOM_VARIANT, CMS_LOGIN_DIALOG_PASSWORD); - gd = CmsUtils.fillWidth(); - gd.widthHint = textWidth; - passwordT.setLayoutData(gd); - - TraverseListener tl = new TraverseListener() { - private static final long serialVersionUID = -1158892811534971856L; - - public void keyTraversed(TraverseEvent e) { - if (e.detail == SWT.TRAVERSE_RETURN) - login(); - } - }; - credentialsBlock.addTraverseListener(tl); - usernameT.addTraverseListener(tl); - passwordT.addTraverseListener(tl); - parent.setTabList(new Control[] { credentialsBlock }); - credentialsBlock.setTabList(new Control[] { usernameT, passwordT }); - // credentialsBlock.setFocus(); - - extendsCredentialsBlock(credentialsBlock, locale, - loginSelectionListener); - if (localeChoice != null) - createLocalesBlock(credentialsBlock); - return credentialsBlock; - } - - /** - * To be overridden in order to provide custome login button and other - * links. - */ - protected void extendsCredentialsBlock(Composite credentialsBlock, - Locale selectedLocale, SelectionListener loginSelectionListener) { - - } - - protected void updateLocale(Locale selectedLocale) { - // save already entered values - String usernameStr = usernameT.getText(); - char[] pwd = passwordT.getTextChars(); - - for (Control child : parent.getChildren()) - child.dispose(); - createContents(parent); - if (parent.getParent() != null) - parent.getParent().layout(); - else - parent.layout(); - usernameT.setText(usernameStr); - passwordT.setTextChars(pwd); - } - - protected Composite createLocalesBlock(final Composite parent) { - Composite c = new Composite(parent, SWT.NONE); - c.setLayout(CmsUtils.noSpaceGridLayout()); - c.setLayoutData(CmsUtils.fillAll()); - - SelectionListener selectionListener = new SelectionAdapter() { - private static final long serialVersionUID = 4891637813567806762L; - - public void widgetSelected(SelectionEvent event) { - Button button = (Button) event.widget; - if (button.getSelection()) { - localeChoice.setSelectedIndex((Integer) event.widget - .getData()); - updateLocale(localeChoice.getSelectedLocale()); - } - }; - }; - - List locales = localeChoice.getLocales(); - for (Integer i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - Button button = new Button(c, SWT.RADIO); - button.setData(i); - button.setText(LocaleUtils.lead(locale.getDisplayName(locale), - locale) + " (" + locale + ")"); - // button.addListener(SWT.Selection, listener); - button.addSelectionListener(selectionListener); - if (i == localeChoice.getSelectedIndex()) - button.setSelection(true); - } - return c; - } - - protected boolean login() { - Subject subject = cmsView.getSubject(); - LoginContext loginContext; - try { - // - // LOGIN - // - new LoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).logout(); - loginContext = new LoginContext(LOGIN_CONTEXT_USER, subject, this); - loginContext.login(); - } catch (FailedLoginException e) { - log.warn(e.getMessage()); - try { - Thread.sleep(3000); - } catch (InterruptedException e2) { - // silent - } -// ErrorFeedback.show("Login failed", e); - return false; - } catch (LoginException e) { - log.error("Cannot login", e); - return false; - } - cmsView.authChange(loginContext); - return true; - } - - protected void logout() { - cmsView.logout(); - cmsView.navigateTo("~"); - } - - @Override - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) - ((NameCallback) callback).setName(usernameT.getText()); - else if (callback instanceof PasswordCallback) - ((PasswordCallback) callback).setPassword(passwordT - .getTextChars()); - else if (callback instanceof HttpRequestCallback) - ((HttpRequestCallback) callback).setRequest(RWT.getRequest()); - else if (callback instanceof LanguageCallback - && localeChoice != null) - ((LanguageCallback) callback).setLocale(localeChoice - .getSelectedLocale()); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLoginShell.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLoginShell.java deleted file mode 100644 index 29a3f54e6..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLoginShell.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.widgets.auth; - -import org.argeo.cms.CmsView; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Widget; - -/** The site-related user menu */ -public class CmsLoginShell extends CmsLogin { - private final Shell shell; - - public CmsLoginShell(CmsView cmsView) { - super(cmsView); - shell = createShell(); - shell.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - createUi(shell); - } - - /** To be overridden. */ - protected Shell createShell() { - Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); - shell.setMaximized(true); - return shell; - } - - /** To be overridden. */ - public void open() { - shell.open(); - } - - @Override - protected boolean login() { - boolean success = false; - try { - success = super.login(); - return success; - } finally { - if (success) - closeShell(); - else { - for (Control child : shell.getChildren()) - child.dispose(); - createUi(shell); - shell.layout(); - // TODO error message - } - } - } - - @Override - protected void logout() { - closeShell(); - super.logout(); - } - - protected void closeShell() { - if (!shell.isDisposed()) { - shell.close(); - shell.dispose(); - } - } - - public Shell getShell() { - return shell; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java deleted file mode 100644 index 55190d39c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java +++ /dev/null @@ -1,291 +0,0 @@ -package org.argeo.cms.widgets.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.cms.internal.auth.LocaleChoice; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** - * A composite that can populate itself based on {@link Callback}s. It can be - * used directly as a {@link CallbackHandler} or be used by one by calling the - * {@link #createCallbackHandlers(Callback[])}. - *

- * Supported standard {@link Callback}s are:
- *

    - *
  • {@link PasswordCallback}
  • - *
  • {@link NameCallback}
  • - *
  • {@link TextOutputCallback}
  • - *
- *

- *

- * Supported Argeo {@link Callback}s are:
- *

    - *
  • {@link LocaleChoice}
  • - *
- *

- */ -public class CompositeCallbackHandler extends Composite implements - CallbackHandler { - private static final long serialVersionUID = -928223893722723777L; - - private boolean wasUsedAlready = false; - private boolean isSubmitted = false; - private boolean isCanceled = false; - - public CompositeCallbackHandler(Composite parent, int style) { - super(parent, style); - } - - @Override - public synchronized void handle(final Callback[] callbacks) - throws IOException, UnsupportedCallbackException { - // reset - if (wasUsedAlready && !isSubmitted() && !isCanceled()) { - cancel(); - for (Control control : getChildren()) - control.dispose(); - isSubmitted = false; - isCanceled = false; - } - - for (Callback callback : callbacks) - checkCallbackSupported(callback); - // create controls synchronously in the UI thread - getDisplay().syncExec(new Runnable() { - - @Override - public void run() { - createCallbackHandlers(callbacks); - } - }); - - if (!wasUsedAlready) - wasUsedAlready = true; - -// while (!isSubmitted() && !isCanceled()) { -// try { -// wait(1000l); -// } catch (InterruptedException e) { -// // silent -// } -// } - -// cleanCallbacksAfterCancel(callbacks); - } - - public void checkCallbackSupported(Callback callback) - throws UnsupportedCallbackException { - if (callback instanceof TextOutputCallback - || callback instanceof NameCallback - || callback instanceof PasswordCallback - || callback instanceof LocaleChoice) { - return; - } else { - throw new UnsupportedCallbackException(callback); - } - } - - /** - * Set writable callbacks to null if the handle is canceled (check is done - * by the method) - */ - public void cleanCallbacksAfterCancel(Callback[] callbacks) { - if (isCanceled()) { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(null); - } else if (callback instanceof PasswordCallback) { - PasswordCallback pCallback = (PasswordCallback) callback; - char[] arr = pCallback.getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - pCallback.setPassword(null); - } - } - } - } - } - - public void createCallbackHandlers(Callback[] callbacks) { - Composite composite = this; - for (int i = 0; i < callbacks.length; i++) { - Callback callback = callbacks[i]; - if (callback instanceof TextOutputCallback) { - createLabelTextoutputHandler(composite, - (TextOutputCallback) callback); - } else if (callback instanceof NameCallback) { - createNameHandler(composite, (NameCallback) callback); - } else if (callback instanceof PasswordCallback) { - createPasswordHandler(composite, (PasswordCallback) callback); - } else if (callback instanceof LocaleChoice) { - createLocaleHandler(composite, (LocaleChoice) callback); - } - } - } - - protected Text createNameHandler(Composite composite, - final NameCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD - | SWT.BORDER); - if (callback.getDefaultName() != null) { - // set default value, if provided - text.setText(callback.getDefaultName()); - callback.setName(callback.getDefaultName()); - } - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7300032545287292973L; - - public void modifyText(ModifyEvent event) { - callback.setName(text.getText()); - } - }); - text.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - - text.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8698107785092095713L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - } - }); - return text; - } - - protected Text createPasswordHandler(Composite composite, - final PasswordCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD - | SWT.PASSWORD | SWT.BORDER); - passwordText - .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - passwordText.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -7099363995047686732L; - - public void modifyText(ModifyEvent event) { - callback.setPassword(passwordText.getTextChars()); - } - }); - passwordText.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - return passwordText; - } - - protected Combo createLocaleHandler(Composite composite, - final LocaleChoice callback) { - String[] labels = callback.getSupportedLocalesLabels(); - if (labels.length == 0) - return null; - Label label = new Label(composite, SWT.NONE); - label.setText("Language"); - - final Combo combo = new Combo(composite, SWT.READ_ONLY); - combo.setItems(labels); - combo.select(callback.getDefaultIndex()); - combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - combo.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 38678989091946277L; - - @Override - public void widgetSelected(SelectionEvent e) { - callback.setSelectedIndex(combo.getSelectionIndex()); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }); - return combo; - } - - protected Label createLabelTextoutputHandler(Composite composite, - final TextOutputCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getMessage()); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); - data.horizontalSpan = 2; - label.setLayoutData(data); - return label; - // TODO: find a way to pass this information - // int messageType = callback.getMessageType(); - // int dialogMessageType = IMessageProvider.NONE; - // switch (messageType) { - // case TextOutputCallback.INFORMATION: - // dialogMessageType = IMessageProvider.INFORMATION; - // break; - // case TextOutputCallback.WARNING: - // dialogMessageType = IMessageProvider.WARNING; - // break; - // case TextOutputCallback.ERROR: - // dialogMessageType = IMessageProvider.ERROR; - // break; - // } - // setMessage(callback.getMessage(), dialogMessageType); - } - - synchronized boolean isSubmitted() { - return isSubmitted; - } - - synchronized boolean isCanceled() { - return isCanceled; - } - - protected synchronized void submit() { - isSubmitted = true; - notifyAll(); - } - - protected synchronized void cancel() { - isCanceled = true; - notifyAll(); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java deleted file mode 100644 index b8de34b7c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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. - */ -package org.argeo.cms.widgets.auth; - -import javax.security.auth.callback.CallbackHandler; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Default authentication dialog, to be used as {@link CallbackHandler}. */ -public class DefaultLoginDialog extends AbstractLoginDialog { - private static final long serialVersionUID = -8551827590693035734L; - - public DefaultLoginDialog() { - this(Display.getCurrent().getActiveShell()); - } - - public DefaultLoginDialog(Shell parentShell) { - super(parentShell); - } - - protected Point getInitialSize() { - return new Point(350, 180); - } - - @Override - protected Control createContents(Composite parent) { - Control control = super.createContents(parent); - parent.pack(); - - // Move the dialog to the center of the top level shell. - Rectangle shellBounds; - if (Display.getCurrent().getActiveShell() != null) // RCP - shellBounds = Display.getCurrent().getActiveShell().getBounds(); - else - shellBounds = Display.getCurrent().getBounds();// RAP - Point dialogSize = parent.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - parent.setLocation(x, y); - return control; - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - CompositeCallbackHandler composite = new CompositeCallbackHandler( - dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - composite.createCallbackHandlers(getCallbacks()); - return composite; - } - - public void internalHandle() { - } -} diff --git a/org.argeo.cms/src/org/argeo/security/core/AbstractSystemExecution.java b/org.argeo.cms/src/org/argeo/security/core/AbstractSystemExecution.java deleted file mode 100644 index 9c3e5cd83..000000000 --- a/org.argeo.cms/src/org/argeo/security/core/AbstractSystemExecution.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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. - */ -package org.argeo.security.core; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; - -/** Provides base method for executing code with system authorization. */ -public abstract class AbstractSystemExecution { - private final static Log log = LogFactory.getLog(AbstractSystemExecution.class); - private final Subject subject = new Subject(); - - private final String loginModule = "SYSTEM"; - - /** - * Authenticate the calling thread to the underlying - * {@link AuthenticationManager} - */ - protected void authenticateAsSystem() { - ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - try { - LoginContext lc = new LoginContext(loginModule, subject); - lc.login(); - } catch (LoginException e) { - throw new CmsException("Cannot login as system", e); - } finally { - Thread.currentThread().setContextClassLoader(origClassLoader); - } - if (log.isTraceEnabled()) - log.trace("System authenticated"); - } - - protected void deauthenticateAsSystem() { - ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - try { - LoginContext lc = new LoginContext(loginModule, subject); - lc.logout(); - } catch (LoginException e) { - throw new CmsException("Cannot logout as system", e); - } finally { - Thread.currentThread().setContextClassLoader(origClassLoader); - } - } - - protected Subject getSubject() { - return subject; - } -} diff --git a/org.argeo.cms/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java b/org.argeo.cms/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java deleted file mode 100644 index aa3827c92..000000000 --- a/org.argeo.cms/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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. - */ -package org.argeo.security.core; - -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.List; - -import javax.security.auth.Subject; - -import org.eclipse.gemini.blueprint.context.DependencyInitializationAwareBeanPostProcessor; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.support.AbstractBeanFactory; -import org.springframework.beans.factory.support.SecurityContextProvider; -import org.springframework.beans.factory.support.SimpleSecurityContextProvider; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Executes with a system authentication the instantiation and initialization - * methods of the application context where it has been defined. - */ -public class AuthenticatedApplicationContextInitialization extends - AbstractSystemExecution implements - DependencyInitializationAwareBeanPostProcessor, ApplicationContextAware { - /** If non empty, restricts to these beans */ - private List beanNames = new ArrayList(); - - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (beanNames.size() == 0 || beanNames.contains(beanName)) - authenticateAsSystem(); - return bean; - } - - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - if (beanNames.size() == 0 || beanNames.contains(beanName)) - deauthenticateAsSystem(); - return bean; - } - - public void setBeanNames(List beanNames) { - this.beanNames = beanNames; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - if (applicationContext.getAutowireCapableBeanFactory() instanceof AbstractBeanFactory) { - final AbstractBeanFactory beanFactory = ((AbstractBeanFactory) applicationContext - .getAutowireCapableBeanFactory()); - // retrieve subject's access control context - // and set it as the bean factory security context - Subject.doAs(getSubject(), new PrivilegedAction() { - @Override - public Void run() { - SecurityContextProvider scp = new SimpleSecurityContextProvider( - AccessController.getContext()); - beanFactory.setSecurityContextProvider(scp); - return null; - } - }); - } - } -} diff --git a/org.argeo.cms/src/org/argeo/security/core/OsgiModuleLabel.java b/org.argeo.cms/src/org/argeo/security/core/OsgiModuleLabel.java deleted file mode 100644 index 45c9e16b0..000000000 --- a/org.argeo.cms/src/org/argeo/security/core/OsgiModuleLabel.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.security.core; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; - -/** - * Logs the name and version of an OSGi bundle based on its - * {@link BundleContext}. - */ -public class OsgiModuleLabel { - private final static Log log = LogFactory.getLog(OsgiModuleLabel.class); - - private Bundle bundle; - - public OsgiModuleLabel() { - } - - /** Sets without logging. */ - public OsgiModuleLabel(Bundle bundle) { - this.bundle = bundle; - } - - /** - * Retrieved bundle from a bundle context and logs it. Typically to be set - * as a Spring bean. - */ - public void setBundleContext(BundleContext bundleContext) { - this.bundle = bundleContext.getBundle(); - log.info(msg()); - } - - public String msg() { - String name = bundle.getHeaders().get(Constants.BUNDLE_NAME).toString(); - String symbolicName = bundle.getSymbolicName(); - String version = bundle.getVersion().toString(); - return name + " v" + version + " (" + symbolicName + ")"; - } -} diff --git a/org.argeo.cms/src/org/argeo/security/core/SimpleRoleRegistration.java b/org.argeo.cms/src/org/argeo/security/core/SimpleRoleRegistration.java deleted file mode 100644 index 58f6686ac..000000000 --- a/org.argeo.cms/src/org/argeo/security/core/SimpleRoleRegistration.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.argeo.security.core; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.transaction.UserTransaction; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Register one or many roles via a user admin service. Does nothing if the role - * is already registered. - */ -public class SimpleRoleRegistration implements Runnable { - private final static Log log = LogFactory - .getLog(SimpleRoleRegistration.class); - - private String role; - private List roles = new ArrayList(); - private UserAdmin userAdmin; - private UserTransaction userTransaction; - - @Override - public void run() { - try { - userTransaction.begin(); - if (role != null && !roleExists(role)) - newRole(toDn(role)); - - for (String r : roles) - if (!roleExists(r)) - newRole(toDn(r)); - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - log.error("Cannot rollback", e1); - } - throw new CmsException("Cannot add roles", e); - } - } - - private boolean roleExists(String role) { - return userAdmin.getRole(toDn(role).toString()) != null; - } - - protected void newRole(LdapName r) { - userAdmin.createRole(r.toString(), Role.GROUP); - log.info("Added role " + r + " required by application."); - } - - public void register(UserAdmin userAdminService, Map properties) { - this.userAdmin = userAdminService; - run(); - } - - protected LdapName toDn(String name) { - try { - return new LdapName("cn=" + name + ",ou=roles,ou=node"); - } catch (InvalidNameException e) { - throw new CmsException("Badly formatted role name " + name, e); - } - } - - public void setRole(String role) { - this.role = role; - } - - public void setRoles(List roles) { - this.roles = roles; - } - - public void setUserAdmin(UserAdmin userAdminService) { - this.userAdmin = userAdminService; - } - - public void setUserTransaction(UserTransaction userTransaction) { - this.userTransaction = userTransaction; - } - -} diff --git a/pom.xml b/pom.xml index 3f8cc3384..9c1f8aec7 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,8 @@ org.argeo.node.api org.argeo.cms + org.argeo.cms.ui - org.argeo.security.ui org.argeo.cms.ui.workbench org.argeo.cms.ui.workbench.rap