CCM NG: Primarly bug fixing for the selection models

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3897 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-02-26 17:36:33 +00:00
parent 2d30f3d69f
commit beb702efd9
22 changed files with 599 additions and 121 deletions

View File

@ -6,7 +6,7 @@
</Console> </Console>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Root level="info"> <Root level="warn">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
</Root> </Root>
<Logger name="com.arsdigita.ui.admin.AdminServlet" <Logger name="com.arsdigita.ui.admin.AdminServlet"
@ -22,7 +22,10 @@
level="debug"> level="debug">
</Logger> </Logger>
<Logger name="com.arsdigita.templating.PatternStylesheetResolver" <Logger name="com.arsdigita.templating.PatternStylesheetResolver"
level="debug"> level="info">
</Logger>
<Logger name="com.arsdigita.templating.SimpleURIResolver"
level="warn">
</Logger> </Logger>
<Logger name="com.arsdigita.web.CCMDispatcherServlet" <Logger name="com.arsdigita.web.CCMDispatcherServlet"
level="debug"> level="debug">

View File

@ -26,20 +26,28 @@ import com.arsdigita.bebop.event.EventListenerList;
import com.arsdigita.util.Assert; import com.arsdigita.util.Assert;
import com.arsdigita.util.Lockable; import com.arsdigita.util.Lockable;
/** import java.util.stream.Stream;
* A standard implementation of <code>SingleSelectionModel</code> and
* <code>Lockable</code>. Those wishing to define a SingleSelectionModel
* will ordinarily want to extend this class.
*
* @version $Id: AbstractSingleSelectionModel.java 287 2005-02-22 00:29:02Z sskracic $
*/
public abstract class AbstractSingleSelectionModel
implements SingleSelectionModel, Lockable {
private EventListenerList m_listeners; /**
* A standard implementation of <code>SingleSelectionModel</code> and
* <code>Lockable</code>. Those wishing to define a SingleSelectionModel will
* ordinarily want to extend this class.
*
* jensp: Added generics and Java 8 streams instead of using an iterator.
*
* @param <T>
*
* @author Unknown
* @author Jens Pelzetter (jensp)
*/
public abstract class AbstractSingleSelectionModel<T>
implements SingleSelectionModel<T>, Lockable {
private final EventListenerList m_listeners;
private boolean m_locked; private boolean m_locked;
/** Creates a new AbstractSingleSelectionModel. /**
* Creates a new AbstractSingleSelectionModel.
*/ */
public AbstractSingleSelectionModel() { public AbstractSingleSelectionModel() {
m_listeners = new EventListenerList(); m_listeners = new EventListenerList();
@ -49,51 +57,57 @@ public abstract class AbstractSingleSelectionModel
* Returns <code>true</code> if there is a selected element. * Returns <code>true</code> if there is a selected element.
* *
* @param state the state of the current request * @param state the state of the current request
*
* @return <code>true</code> if there is a selected component; * @return <code>true</code> if there is a selected component;
* <code>false</code> otherwise. * <code>false</code> otherwise.
*/ */
public boolean isSelected(PageState state) { @Override
public boolean isSelected(final PageState state) {
return getSelectedKey(state) != null; return getSelectedKey(state) != null;
} }
public abstract Object getSelectedKey(PageState state); @Override
public abstract T getSelectedKey(final PageState state);
public abstract void setSelectedKey(PageState state, Object key); @Override
public abstract void setSelectedKey(final PageState state, final T key);
public void clearSelection(PageState state) { @Override
public void clearSelection(final PageState state) {
setSelectedKey(state, null); setSelectedKey(state, null);
} }
// Selection change events // Selection change events
@Override
public void addChangeListener(ChangeListener l) { public void addChangeListener(final ChangeListener changeListener) {
Assert.isUnlocked(this); Assert.isUnlocked(this);
m_listeners.add(ChangeListener.class, l); m_listeners.add(ChangeListener.class, changeListener);
} }
public void removeChangeListener(ChangeListener l) { @Override
public void removeChangeListener(final ChangeListener changeListener) {
Assert.isUnlocked(this); Assert.isUnlocked(this);
m_listeners.remove(ChangeListener.class, l); m_listeners.remove(ChangeListener.class, changeListener);
} }
protected void fireStateChanged(PageState state) { protected void fireStateChanged(final PageState state) {
Iterator i = m_listeners.getListenerIterator(ChangeListener.class); final ChangeEvent event = new ChangeEvent(this, state);
ChangeEvent e = null; final Iterator<ChangeListener> iterator = m_listeners
.getListenerIterator(ChangeListener.class);
while (i.hasNext()) { while(iterator.hasNext()) {
if ( e == null ) { iterator.next().stateChanged(event);
e = new ChangeEvent(this, state);
}
((ChangeListener) i.next()).stateChanged(e);
} }
} }
// implement Lockable // implement Lockable
@Override
public void lock() { public void lock() {
m_locked = true; m_locked = true;
} }
@Override
public final boolean isLocked() { public final boolean isLocked() {
return m_locked; return m_locked;
} }
} }

View File

@ -29,7 +29,7 @@ import com.arsdigita.xml.Element;
* of certain parts of the screen. Therefore the label has to use a * of certain parts of the screen. Therefore the label has to use a
* GlobalizedMessage for the information presented. * GlobalizedMessage for the information presented.
* *
* A Label is meant to provide semantically relevant informatin and may not be * A Label is meant to provide semantically relevant information and may not be
* used for fixed arbitrary Text. Use Embedded instead. * used for fixed arbitrary Text. Use Embedded instead.
* *
* (Previous usage: can be used to generate either some static, fixed * (Previous usage: can be used to generate either some static, fixed

View File

@ -22,12 +22,15 @@ import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.util.Assert; import com.arsdigita.util.Assert;
/** /**
* An implementation of {@link SingleSelectionModel} that uses * An implementation of {@link SingleSelectionModel} that uses a state parameter
* a state parameter for managing the currently selected key. * for managing the currently selected key.
* <p> * <p>
* *
* A typical use case for this class is as follows. * A typical use case for this class is as follows.
* <blockquote><pre><code>public TheConstructor() { * <blockquote>
* <pre>
* <code>
* public TheConstructor() {
* m_parameter = new StringParameter("my_key"); * m_parameter = new StringParameter("my_key");
* m_sel = new ParameterSingleSelectionModel(m_parameter); * m_sel = new ParameterSingleSelectionModel(m_parameter);
* } * }
@ -35,22 +38,29 @@ import com.arsdigita.util.Assert;
* public void register(Page p) { * public void register(Page p) {
* p.addComponent(this); * p.addComponent(this);
* p.addComponentStateParam(this, m_param); * p.addComponentStateParam(this, m_param);
* }</code></pre></blockquote> * }
* </code>
* </pre>
* </blockquote>
*
* jensp 2016-02-26: Added generics
*
* @param <T> Generics parameter
* *
* @author Stanislav Freidin * @author Stanislav Freidin
* @version $Id: ParameterSingleSelectionModel.java 287 2005-02-22 00:29:02Z sskracic $ * @author Jens Pelzetter
*
*/ */
public class ParameterSingleSelectionModel public class ParameterSingleSelectionModel<T>
extends AbstractSingleSelectionModel { extends AbstractSingleSelectionModel<T> {
private final ParameterModel m_parameter;
private ParameterModel m_parameter;
/** /**
* Constructs a new ParameterSingleSelectionModel. * Constructs a new ParameterSingleSelectionModel.
* *
* @param m the parameter model that will be used to * @param m the parameter model that will be used to keep track of the
* keep track of the currently selected key * currently selected key
*/ */
public ParameterSingleSelectionModel(ParameterModel m) { public ParameterSingleSelectionModel(ParameterModel m) {
super(); super();
@ -62,17 +72,21 @@ public class ParameterSingleSelectionModel
* Returns the key that identifies the selected element. * Returns the key that identifies the selected element.
* *
* @param state a <code>PageState</code> value * @param state a <code>PageState</code> value
*
* @return a <code>String</code> value. * @return a <code>String</code> value.
*/ */
public Object getSelectedKey(PageState state) { @Override
@SuppressWarnings("unchecked")
public T getSelectedKey(final PageState state) {
final FormModel model = state.getPage().getStateModel(); final FormModel model = state.getPage().getStateModel();
if (model.containsFormParam(m_parameter)) { if (model.containsFormParam(m_parameter)) {
return state.getValue(m_parameter); return (T) state.getValue(m_parameter);
} else { } else {
return null; return null;
} }
} }
@Override
public final ParameterModel getStateParameter() { public final ParameterModel getStateParameter() {
return m_parameter; return m_parameter;
} }
@ -80,10 +94,11 @@ public class ParameterSingleSelectionModel
/** /**
* Set the selected key. * Set the selected key.
* *
* @param state represents the state of the current request * @param state represents the state of the current request
* @param newKey the new selected key * @param newKey the new selected key
*/ */
public void setSelectedKey(PageState state, Object newKey) { @Override
public void setSelectedKey(final PageState state, final Object newKey) {
final Object oldKey = getSelectedKey(state); final Object oldKey = getSelectedKey(state);
if (Assert.isEnabled()) { if (Assert.isEnabled()) {
@ -103,4 +118,5 @@ public class ParameterSingleSelectionModel
fireStateChanged(state); fireStateChanged(state);
} }
} }

View File

@ -38,10 +38,14 @@ import com.arsdigita.bebop.parameters.ParameterModel;
* key's <code>toString</code> method produces a representation of the key * key's <code>toString</code> method produces a representation of the key
* that can be used in URL strings and hidden form controls. * that can be used in URL strings and hidden form controls.
* *
* Edit for CCM NG: Added generics.
*
* @param <T> Type for the key
*
* @author David Lutterkort * @author David Lutterkort
* @version $Id: SingleSelectionModel.java 287 2005-02-22 00:29:02Z sskracic $ * @author Jens Pelzetter
*/ */
public interface SingleSelectionModel { public interface SingleSelectionModel<T> {
/** /**
* Returns <code>true</code> if there is a selected element. * Returns <code>true</code> if there is a selected element.
@ -58,7 +62,7 @@ public interface SingleSelectionModel {
* @param state a <code>PageState</code> value * @param state a <code>PageState</code> value
* @return a <code>String</code> value. * @return a <code>String</code> value.
*/ */
Object getSelectedKey(PageState state); T getSelectedKey(PageState state);
/** /**
* Sets the selected key. If <code>key</code> is not in the collection of * Sets the selected key. If <code>key</code> is not in the collection of
@ -70,7 +74,7 @@ public interface SingleSelectionModel {
* @throws IllegalArgumentException if the supplied <code>key</code> can not * @throws IllegalArgumentException if the supplied <code>key</code> can not
* be selected in the context of the current request. * be selected in the context of the current request.
*/ */
void setSelectedKey(PageState state, Object key); void setSelectedKey(PageState state, T key);
/** /**
* Clears the selection. * Clears the selection.
@ -84,16 +88,16 @@ public interface SingleSelectionModel {
* Adds a change listener to the model. The listener's * Adds a change listener to the model. The listener's
* <code>stateChanged</code> method is called whenever the selected key changes. * <code>stateChanged</code> method is called whenever the selected key changes.
* *
* @param l a listener to notify when the selected key changes * @param changeListener a listener to notify when the selected key changes
*/ */
void addChangeListener(ChangeListener l); void addChangeListener(ChangeListener changeListener);
/** /**
* Removes a change listener from the model. * Removes a change listener from the model.
* *
* @param l the listener to remove * @param changeListener the listener to remove
*/ */
void removeChangeListener(ChangeListener l); void removeChangeListener(ChangeListener changeListener);
/** /**
* Returns the state parameter that will be used to keep track * Returns the state parameter that will be used to keep track

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.bebop;
import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.event.PrintListener;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class Text extends SimpleComponent {
private String text;
private boolean outputEscaped;
private PrintListener printListener;
public Text() {
this("");
}
public Text(final String text) {
this.text = text;
outputEscaped = true;
}
public Text(final PrintListener printListener) {
this();
this.printListener = printListener;
}
public String getText() {
return text;
}
public void setText(final String text) {
this.text = text;
}
public boolean isOutputEscaped() {
return outputEscaped;
}
public void setOutputEscaped(final boolean outputEscaped) {
this.outputEscaped = outputEscaped;
}
public void setPrintListener(final PrintListener printListener) {
if (printListener == null) {
throw new IllegalArgumentException("PrintListener can't be null");
}
this.printListener = printListener;
}
@Override
public void generateXML(final PageState state, final Element parent) {
if (!isVisible(state)) {
return;
}
final Text target = firePrintEvent(state);
final Element textElem = parent.newChildElement("bebop:text",
BEBOP_XML_NS);
target.exportAttributes(textElem);
if (outputEscaped) {
textElem.addAttribute("escape", "no");
} else {
textElem.addAttribute("escape", "yes");
}
textElem.setText(target.getText());
}
protected Text firePrintEvent(final PageState state) {
final Text component;
if (printListener == null) {
component = this;
} else {
try {
component = (Text) this.clone();
printListener.prepare(new PrintEvent(this, state, component));
} catch (CloneNotSupportedException ex) {
throw new UncheckedWrapperException(
"Could not clone Text component for PrintListener. "
+ "This propaby indicates a serious programming error.");
}
}
return component;
}
}

View File

@ -28,22 +28,25 @@ import java.util.NoSuchElementException;
*/ */
public class EventListenerList extends javax.swing.event.EventListenerList { public class EventListenerList extends javax.swing.event.EventListenerList {
private static final long serialVersionUID = -1930203818146602205L;
/** /**
* Append all the event listeners from <code>l</code>. * Append all the event listeners from <code>l</code>.
* *
* @param l The list of listeners to copy from * @param list The list of listeners to copy from
* *
* @pre l != null * @pre l != null
*/ */
public void addAll(EventListenerList l) { public void addAll(final EventListenerList list) {
if ( l.listenerList.length == 0 ) if ( list.listenerList.length == 0 ) {
return; return;
}
Object[] tmp = new Object[listenerList.length + l.listenerList.length]; Object[] tmp = new Object[listenerList.length + list.listenerList.length];
System.arraycopy(listenerList, 0, tmp, 0, listenerList.length); System.arraycopy(listenerList, 0, tmp, 0, listenerList.length);
System.arraycopy(l.listenerList, 0, System.arraycopy(list.listenerList, 0,
tmp, listenerList.length, l.listenerList.length); tmp, listenerList.length, list.listenerList.length);
listenerList = tmp; listenerList = tmp;
} }
@ -53,15 +56,17 @@ public class EventListenerList extends javax.swing.event.EventListenerList {
* {@link javax.swing.event.EventListenerList Swing's * {@link javax.swing.event.EventListenerList Swing's
* <code>EventListenerList</code>}. * <code>EventListenerList</code>}.
* *
* @param t The class of the event listeners that should be returned * @param <T>
* @param type The class of the event listeners that should be returned
* @return
* *
* @pre t != null * @pre t != null
* */ * */
public Iterator getListenerIterator(final Class t) { public <T> Iterator<T> getListenerIterator(final Class<T> type) {
return new EventListenerIterator(t); return new EventListenerIterator<>(type);
} }
private class EventListenerIterator implements Iterator { private class EventListenerIterator<T> implements Iterator<T> {
/** /**
* The listener we will return with the next call to next(). * The listener we will return with the next call to next().
@ -69,31 +74,35 @@ public class EventListenerList extends javax.swing.event.EventListenerList {
* matching listeners have been returned, in which case _next * matching listeners have been returned, in which case _next
* is -1 * is -1
* */ * */
private int _count; private final int count;
private int _next; private int next;
private Class _t; private final Class<T> type;
EventListenerIterator(Class t) { EventListenerIterator(Class<T> type) {
_count = getListenerList().length; count = getListenerList().length;
_next = -2; next = -2;
_t = t; this.type = type;
findNext(); findNext();
} }
@Override
public boolean hasNext() { public boolean hasNext() {
return (_next < _count); return (next < count);
} }
public Object next() throws NoSuchElementException { @Override
@SuppressWarnings("unchecked")
public T next() throws NoSuchElementException {
if ( ! hasNext() ) { if ( ! hasNext() ) {
throw new NoSuchElementException("Iterator exhausted"); throw new NoSuchElementException("Iterator exhausted");
} }
int result = _next; int result = next;
findNext(); findNext();
return getListenerList()[result+1]; return (T) getListenerList()[result+1];
} }
@Override
public void remove() throws UnsupportedOperationException { public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Removal not supported"); throw new UnsupportedOperationException("Removal not supported");
} }
@ -108,14 +117,14 @@ public class EventListenerList extends javax.swing.event.EventListenerList {
* */ * */
private void findNext() { private void findNext() {
for (int i = _next+2; i<_count; i+=2) { for (int i = next+2; i<count; i+=2) {
if (getListenerList()[i] == _t) { if (getListenerList()[i] == type) {
_next = i; next = i;
return; return;
} }
} }
_next = _count; next = count;
} }
} }
} }

View File

@ -32,6 +32,7 @@ public class LongParameter extends NumberParameter {
super(name); super(name);
} }
@Override
public Object unmarshal(String encoded) { public Object unmarshal(String encoded) {
try { try {
return new Long(encoded); return new Long(encoded);
@ -43,6 +44,7 @@ public class LongParameter extends NumberParameter {
} }
} }
@Override
public Class getValueClass() { public Class getValueClass() {
return Long.class; return Long.class;
} }

View File

@ -33,6 +33,8 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.util.Objects;
/** /**
* Represents the abstract model for a form parameter object. This * Represents the abstract model for a form parameter object. This
* class must be subclassed for each specific data type. * class must be subclassed for each specific data type.
@ -495,4 +497,24 @@ public abstract class ParameterModel implements Lockable {
} }
return false; return false;
} }
@Override
public boolean equals(final Object other) {
if (other == null) {
return false;
}
if (!(other instanceof ParameterModel)) {
return false;
}
return m_name.equals(((ParameterModel) other).getName());
}
@Override
public int hashCode() {
int hash = 7;
hash = 73 * hash + Objects.hashCode(this.m_name);
return hash;
}
} }

View File

@ -18,6 +18,8 @@
*/ */
package com.arsdigita.templating; package com.arsdigita.templating;
import org.apache.logging.log4j.LogManager;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@ -32,7 +34,7 @@ import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver; import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger; import org.apache.logging.log4j.Logger;
/** /**
* An implementation of the URIResolver interface that keeps track of all the * An implementation of the URIResolver interface that keeps track of all the
@ -44,7 +46,7 @@ import org.apache.log4j.Logger;
*/ */
final class SimpleURIResolver implements URIResolver { final class SimpleURIResolver implements URIResolver {
private static final Logger s_log = Logger.getLogger private static final Logger s_log = LogManager.getLogger
(SimpleURIResolver.class); (SimpleURIResolver.class);
private final Set m_uniqueStylesheetURIs; private final Set m_uniqueStylesheetURIs;

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.event.ChangeListener;
import com.arsdigita.bebop.parameters.LongParameter;
import com.arsdigita.bebop.parameters.ParameterModel;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.core.CcmObject;
import org.libreccm.core.CcmObjectRepository;
/**
* @param <T>
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CcmObjectSelectionModel<T extends CcmObject>
implements SingleSelectionModel<Long>{
private final Class<T> clazz;
private final SingleSelectionModel<Long> model;
public CcmObjectSelectionModel(final LongParameter parameter) {
this(null, parameter);
}
public CcmObjectSelectionModel(final String parameterName) {
this(null, new LongParameter(parameterName));
}
// public CcmObjectSelectionModel(final SingleSelectionModel<T> model ) {
// this(null, model);
// }
//
public CcmObjectSelectionModel(final Class<T> clazz,
final String parameterName) {
this(clazz, new LongParameter(parameterName));
}
public CcmObjectSelectionModel(final Class<T> clazz,
final LongParameter parameter) {
this(clazz, new ParameterSingleSelectionModel<>(parameter));
}
public CcmObjectSelectionModel(final Class<T> clazz,
final SingleSelectionModel<Long> model) {
this.clazz = clazz;
this.model = model;
}
@Override
public boolean isSelected(final PageState state) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Long getSelectedKey(final PageState state) {
return model.getSelectedKey(state);
}
@Override
public void setSelectedKey(final PageState state, final Long key) {
model.setSelectedKey(state, key);
}
public T getSelectedObject(final PageState state) {
final Long key = getSelectedKey(state);
final CcmObjectRepository repository = CdiUtil.createCdiUtil().findBean(
CcmObjectRepository.class);
//final T object = repository.findById(key);
throw new UnsupportedOperationException();
}
@Override
public void clearSelection(PageState state) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void addChangeListener(ChangeListener changeListener) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void removeChangeListener(ChangeListener changeListener) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public ParameterModel getStateParameter() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}

View File

@ -20,6 +20,7 @@ package com.arsdigita.ui.admin;
import com.arsdigita.bebop.Label; import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.parameters.BigDecimalParameter; import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.bebop.parameters.LongParameter;
import com.arsdigita.globalization.GlobalizedMessage; import com.arsdigita.globalization.GlobalizedMessage;
/** /**
@ -100,11 +101,11 @@ interface AdminConstants {
/** /**
* Global state parameters. * Global state parameters.
*/ */
BigDecimalParameter GROUP_ID_PARAM = new BigDecimalParameter("group_id"); LongParameter GROUP_ID_PARAM = new LongParameter("group_id");
BigDecimalParameter APPLICATIONS_ID_PARAM = new BigDecimalParameter("application_id"); LongParameter APPLICATIONS_ID_PARAM = new LongParameter("application_id");
BigDecimalParameter USER_ID_PARAM = new BigDecimalParameter("user_id"); LongParameter USER_ID_PARAM = new LongParameter("user_id");
/** /**
* User summary panel. * User summary panel.

View File

@ -18,19 +18,64 @@
*/ */
package com.arsdigita.ui.admin.usersgroupsroles; package com.arsdigita.ui.admin.usersgroupsroles;
import com.arsdigita.bebop.ActionLink;
import com.arsdigita.bebop.BoxPanel; import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.Text;
import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.form.Submit;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.LongParameter;
import com.arsdigita.globalization.GlobalizedMessage;
import static com.arsdigita.ui.admin.AdminUiConstants.*;
/** /**
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
public class UserAdmin extends BoxPanel { public class UserAdmin extends BoxPanel {
private final ParameterSingleSelectionModel<String> selectedUserId;
private final TextField usersTableFilter;
public UserAdmin() { public UserAdmin() {
super(); super();
//add(new Label("User Admin class")); //add(new Label("User Admin class"));
add(new UsersTable()); final Form filterForm = new Form("usersTableFilterForm");
usersTableFilter = new TextField("usersTableFilter");
usersTableFilter.setLabel(new GlobalizedMessage(
"ui.admin.users.table.filter.term", ADMIN_BUNDLE));
filterForm.add(usersTableFilter);
filterForm.add(new Submit(new GlobalizedMessage(
"ui.admin.users.table.filter.submit", ADMIN_BUNDLE)));
final ActionLink clearLink = new ActionLink(new GlobalizedMessage(
"ui.admin.users.table.filter.clear", ADMIN_BUNDLE));
clearLink.addActionListener((e) -> {
final PageState state = e.getPageState();
usersTableFilter.setValue(state, null);
});
filterForm.add(clearLink);
add(filterForm);
selectedUserId = new ParameterSingleSelectionModel<>(USER_ID_PARAM);
final UsersTable usersTable = new UsersTable(usersTableFilter,
selectedUserId);
add(usersTable);
final Text text = new Text();
text.setPrintListener((final PrintEvent e) -> {
final Text target = (Text) e.getTarget();
final PageState state = e.getPageState();
if (selectedUserId.isSelected(state)) {
target.setText(selectedUserId.getSelectedKey(state));
}
});
add(text);
} }
} }

View File

@ -94,16 +94,6 @@ public class UsersGroupsRolesTab extends LayoutPanel {
setBody(body); setBody(body);
} }
private SimpleContainer buildUserAdmin() {
final BoxPanel panel = new BoxPanel();
panel.add(new Label("User Admin"));
return panel;
}
private void addSection(final Label label, private void addSection(final Label label,
final Component component, final Component component,
final BoxPanel panel) { final BoxPanel panel) {

View File

@ -18,9 +18,17 @@
*/ */
package com.arsdigita.ui.admin.usersgroupsroles; package com.arsdigita.ui.admin.usersgroupsroles;
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.Label; import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.PageState; import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.Table; import com.arsdigita.bebop.Table;
import com.arsdigita.bebop.event.TableActionAdapter;
import com.arsdigita.bebop.event.TableActionEvent;
import com.arsdigita.bebop.event.TableActionListener;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.table.TableCellRenderer;
import com.arsdigita.bebop.table.TableColumn; import com.arsdigita.bebop.table.TableColumn;
import com.arsdigita.bebop.table.TableColumnModel; import com.arsdigita.bebop.table.TableColumnModel;
import com.arsdigita.bebop.table.TableModel; import com.arsdigita.bebop.table.TableModel;
@ -52,9 +60,16 @@ public class UsersTable extends Table {
private static final int COL_PRIMARY_EMAIL = 3; private static final int COL_PRIMARY_EMAIL = 3;
private static final int COL_BANNED = 4; private static final int COL_BANNED = 4;
public UsersTable() { private final TextField usersTableFilter;
private final ParameterSingleSelectionModel<String> selectedUserId;
public UsersTable(final TextField usersTableFilter,
final ParameterSingleSelectionModel<String> selectedUserId) {
super(); super();
this.usersTableFilter = usersTableFilter;
this.selectedUserId = selectedUserId;
setEmptyView(new Label(new GlobalizedMessage( setEmptyView(new Label(new GlobalizedMessage(
"ui.admin.users.table.no_users", ADMIN_BUNDLE))); "ui.admin.users.table.no_users", ADMIN_BUNDLE)));
@ -79,7 +94,36 @@ public class UsersTable extends Table {
COL_BANNED, COL_BANNED,
new Label(new GlobalizedMessage( new Label(new GlobalizedMessage(
"ui.admin.users.table.banned", ADMIN_BUNDLE)))); "ui.admin.users.table.banned", ADMIN_BUNDLE))));
columnModel.get(COL_SCREEN_NAME).setCellRenderer(new TableCellRenderer() {
@Override
public Component getComponent(final Table table,
final PageState state,
final Object value,
final boolean isSelected,
final Object key,
final int row,
final int column) {
return new ControlLink((String) value);
}
});
addTableActionListener(new TableActionListener() {
@Override
public void cellSelected(final TableActionEvent event) {
final String key = (String) event.getRowKey();
selectedUserId.setSelectedKey(event.getPageState(), key);
}
@Override
public void headSelected(final TableActionEvent event) {
//Nothing
}
});
setModelBuilder(new UsersTableModelBuilder()); setModelBuilder(new UsersTableModelBuilder());
} }
@ -90,7 +134,7 @@ public class UsersTable extends Table {
public TableModel makeModel(final Table table, final PageState state) { public TableModel makeModel(final Table table, final PageState state) {
table.getRowSelectionModel().clearSelection(state); table.getRowSelectionModel().clearSelection(state);
return new UsersTableModel(); return new UsersTableModel(state);
} }
} }
@ -100,12 +144,24 @@ public class UsersTable extends Table {
private final List<User> users; private final List<User> users;
private int index = -1; private int index = -1;
public UsersTableModel() { public UsersTableModel(final PageState state) {
LOGGER.debug("Creating UsersTableModel..."); LOGGER.debug("Creating UsersTableModel...");
final String filterTerm = (String) usersTableFilter
.getValue(state);
LOGGER.debug("Value of filter is: \"{}\"", filterTerm);
final UserRepository userRepository = CdiUtil.createCdiUtil() final UserRepository userRepository = CdiUtil.createCdiUtil()
.findBean(UserRepository.class); .findBean(UserRepository.class);
users = userRepository.findAll(); if (filterTerm == null || filterTerm.isEmpty()) {
LOGGER.debug("Found {} users in database.", users.size()); users = userRepository.findAll();
LOGGER.debug("Found {} users in database.", users.size());
} else {
users = userRepository.filtered(filterTerm);
LOGGER.debug("Found {} users in database which match the "
+ "filter \"{}\".",
users.size(),
filterTerm);
}
} }
@Override @Override

View File

@ -82,19 +82,19 @@ public abstract class AbstractEntityRepository<K, E> {
protected void applyDefaultEntityGraph(final TypedQuery<E> query) { protected void applyDefaultEntityGraph(final TypedQuery<E> query) {
if (getEntityClass().isAnnotationPresent(DefaultEntityGraph.class)) { if (getEntityClass().isAnnotationPresent(DefaultEntityGraph.class)) {
LOGGER.debug("The following EntityGraphs are available for the " LOGGER.debug("The following EntityGraphs are available for the "
+ "entity class {}:", + "entity class {}:",
getEntityClass().getName()); getEntityClass().getName());
getEntityManager().getEntityGraphs(getEntityClass()).stream() getEntityManager().getEntityGraphs(getEntityClass()).stream()
.forEach(g -> LOGGER.debug("\t{}", g.getName())); .forEach(g -> LOGGER.debug("\t{}", g.getName()));
LOGGER.debug("Entity class {} has default entity graphs:", LOGGER.debug("Entity class {} has default entity graphs:",
getEntityClass().getName()); getEntityClass().getName());
LOGGER.debug("Applying entity graph {}", LOGGER.debug("Applying entity graph {}",
getEntityClass().getAnnotation( getEntityClass().getAnnotation(
DefaultEntityGraph.class).value()); DefaultEntityGraph.class).value());
query.setHint(FETCH_GRAPH_HINT_KEY, query.setHint(FETCH_GRAPH_HINT_KEY,
entityManager.getEntityGraph( entityManager.getEntityGraph(
getEntityClass().getAnnotation( getEntityClass().getAnnotation(
DefaultEntityGraph.class).value())); DefaultEntityGraph.class).value()));
} }
} }
@ -202,14 +202,59 @@ public abstract class AbstractEntityRepository<K, E> {
public List<E> findAll() { public List<E> findAll() {
// We are using the Critiera API here because otherwise we can't // We are using the Critiera API here because otherwise we can't
// pass the type of the entity dynmacially. // pass the type of the entity dynmacially.
return executeCriteriaQuery(createCriteriaQuery());
}
public List<E> findAll(final String entityGraphName) {
@SuppressWarnings("unchecked")
final EntityGraph<E> entityGraph = (EntityGraph<E>) entityManager
.getEntityGraph(
entityGraphName);
return findAll(entityGraph);
}
public List<E> findAll(final EntityGraph<E> entityGraph) {
// We are using the Critiera API here because otherwise we can't
// pass the type of the entity dynmacially.
return executeCriteriaQuery(createCriteriaQuery(), entityGraph);
}
public CriteriaQuery<E> createCriteriaQuery() {
final CriteriaBuilder criteriaBuilder = entityManager final CriteriaBuilder criteriaBuilder = entityManager
.getCriteriaBuilder(); .getCriteriaBuilder();
final CriteriaQuery<E> criteriaQuery = criteriaBuilder.createQuery( final CriteriaQuery<E> criteriaQuery = criteriaBuilder.createQuery(
getEntityClass()); getEntityClass());
final Root<E> root = criteriaQuery.from(getEntityClass()); final Root<E> root = criteriaQuery.from(getEntityClass());
criteriaQuery.select(root); return criteriaQuery.select(root);
}
public CriteriaBuilder getCriteriaBuilder() {
return entityManager.getCriteriaBuilder();
}
public List<E> executeCriteriaQuery(final CriteriaQuery<E> criteriaQuery) {
if (hasDefaultEntityGraph()) {
return executeCriteriaQuery(criteriaQuery, getDefaultEntityGraph());
} else {
final TypedQuery<E> query = entityManager.createQuery(criteriaQuery);
return query.getResultList();
}
}
public List<E> executeCriteriaQuery(final CriteriaQuery<E> criteriaQuery,
final String graphName) {
@SuppressWarnings("unchecked")
final EntityGraph<E> entityGraph = (EntityGraph< E>) entityManager
.getEntityGraph(
graphName);
return executeCriteriaQuery(criteriaQuery, entityGraph);
}
public List<E> executeCriteriaQuery(final CriteriaQuery<E> criteriaQuery,
final EntityGraph<E> entityGraph) {
final TypedQuery<E> query = entityManager.createQuery(criteriaQuery); final TypedQuery<E> query = entityManager.createQuery(criteriaQuery);
query.setHint(FETCH_GRAPH_HINT_KEY, entityGraph);
return query.getResultList(); return query.getResultList();
} }
@ -251,4 +296,19 @@ public abstract class AbstractEntityRepository<K, E> {
entityManager.remove(entity); entityManager.remove(entity);
} }
protected boolean hasDefaultEntityGraph() {
return getEntityClass().isAnnotationPresent(DefaultEntityGraph.class);
}
protected String getDefaultEntityGraph() {
if (hasDefaultEntityGraph()) {
return getEntityClass().getAnnotation(DefaultEntityGraph.class)
.value();
} else {
throw new IllegalArgumentException(String.format(
"Entity class \"%s\" has no DefaultEntityGraph!",
getEntityClass().getName()));
}
}
} }

View File

@ -67,7 +67,14 @@ import javax.xml.bind.annotation.XmlTransient;
query = "SELECT u FROM User u WHERE u.name = :name"), query = "SELECT u FROM User u WHERE u.name = :name"),
@NamedQuery(name = "User.findByEmailAddress", @NamedQuery(name = "User.findByEmailAddress",
query = "SELECT u FROM User u WHERE " query = "SELECT u FROM User u WHERE "
+ "u.primaryEmailAddress.address = :emailAddress") + "u.primaryEmailAddress.address = :emailAddress"),
@NamedQuery(
name = "User.filterByNameAndEmail",
query = "SELECT u FROM User u WHERE "
+ "LOWER(u.name) LIKE CONCAT(LOWER(:term), '%') "
+ "OR LOWER(u.givenName) LIKE CONCAT(LOWER(:term), '%') "
+ "OR LOWER(u.familyName) LIKE CONCAT(LOWER(:term), '%') "
+ "OR LOWER(u.primaryEmailAddress.address) LIKE CONCAT('%', LOWER(:term), '%')")
}) })
@NamedEntityGraphs({ @NamedEntityGraphs({
@NamedEntityGraph( @NamedEntityGraph(

View File

@ -21,9 +21,7 @@ package org.libreccm.security;
import org.libreccm.core.AbstractEntityRepository; import org.libreccm.core.AbstractEntityRepository;
import java.util.List; import java.util.List;
import java.util.Optional;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityGraph; import javax.persistence.EntityGraph;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -126,13 +124,6 @@ public class UserRepository extends AbstractEntityRepository<Long, User> {
applyDefaultEntityGraph(query); applyDefaultEntityGraph(query);
return getSingleResultOrNull(query); return getSingleResultOrNull(query);
// final List<User> result = query.getResultList();
// if (result.isEmpty()) {
// return null;
// } else {
// return result.get(0);
// }
} }
public User findByEmailAddress(final String emailAddress, public User findByEmailAddress(final String emailAddress,
@ -150,8 +141,16 @@ public class UserRepository extends AbstractEntityRepository<Long, User> {
"User.findByEmailAddress", User.class); "User.findByEmailAddress", User.class);
query.setParameter("emailAddress", emailAddress); query.setParameter("emailAddress", emailAddress);
query.setHint(FETCH_GRAPH_HINT_KEY, entityGraph); query.setHint(FETCH_GRAPH_HINT_KEY, entityGraph);
return getSingleResultOrNull(query); return getSingleResultOrNull(query);
} }
public List<User> filtered(final String term) {
final TypedQuery<User> query = getEntityManager().createNamedQuery(
"User.filterByNameAndEmail", User.class);
query.setParameter("term", term);
return query.getResultList();
}
} }

View File

@ -165,3 +165,7 @@ ui.admin.users.table.givenname=Given name
ui.admin.users.table.familyname=Family name ui.admin.users.table.familyname=Family name
ui.admin.users.table.primary_email=E-Mail ui.admin.users.table.primary_email=E-Mail
ui.admin.users.table.banned=Banned ui.admin.users.table.banned=Banned
ui.admin.users.table.filter.term=Filter users
ui.admin.users.table.filter.submit=Apply
ui.admin.users.table.no_users=No users matching users found.
ui.admin.users.table.filter.clear=Clear filter

View File

@ -165,3 +165,7 @@ ui.admin.users.table.givenname=Vorname
ui.admin.users.table.familyname=Familienname ui.admin.users.table.familyname=Familienname
ui.admin.users.table.primary_email=E-Mail ui.admin.users.table.primary_email=E-Mail
ui.admin.users.table.banned=Gesperrt ui.admin.users.table.banned=Gesperrt
ui.admin.users.table.filter.term=Benutzer filtern
ui.admin.users.table.filter.submit=Anwenden
ui.admin.users.table.no_users=Keine auf den aktuellen Filter passenden Benutzer gefunden.
ui.admin.users.table.filter.clear=Filter zur\u00fccksetzen

View File

@ -138,3 +138,7 @@ ui.admin.users.table.givenname=Given name
ui.admin.users.table.familyname=Family name ui.admin.users.table.familyname=Family name
ui.admin.users.table.primary_email=E-Mail ui.admin.users.table.primary_email=E-Mail
ui.admin.users.table.banned=Banned ui.admin.users.table.banned=Banned
ui.admin.users.table.filter.term=Filter users
ui.admin.users.table.filter.submit=Apply
ui.admin.users.table.no_users=No users matching users found.
ui.admin.users.table.filter.clear=Clear filter

View File

@ -129,3 +129,7 @@ ui.admin.users.table.givenname=Given name
ui.admin.users.table.familyname=Family name ui.admin.users.table.familyname=Family name
ui.admin.users.table.primary_email=E-Mail ui.admin.users.table.primary_email=E-Mail
ui.admin.users.table.banned=Banned ui.admin.users.table.banned=Banned
ui.admin.users.table.filter.term=Filter users
ui.admin.users.table.filter.submit=Apply
ui.admin.users.table.no_users=No users matching users found.
ui.admin.users.table.filter.clear=Clear filter