Commit dbece192 authored by Guus der Kinderen's avatar Guus der Kinderen Committed by daryl herzmann

Avatar resizer plugin inlining (#914)

* AvatarPlugin: Add prior-to-openfire 4.2.0. restriction

* Merged avatarResizer plugin with Openfire core (prevents OF-1145 & OF-1193)
parent 5f89a1ad
/*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* 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.jivesoftware.openfire.ldap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.jivesoftware.openfire.vcard.DefaultVCardProvider;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.Base64;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Read-only LDAP provider for vCards.Configuration consists of adding a provider:
* <p>
* <tt>provider.vcard.className = org.jivesoftware.openfire.ldap.LdapVCardProvider</tt>
* </p>
* <p>and an xml vcard-mapping in the system properties.</p>
* <p>
* The vcard attributes can be configured by adding an <code>attrs="attr1,attr2"</code>
* attribute to the vcard elements.</p>
* <p>
* Arbitrary text can be used for the element values as well as <code>MessageFormat</code>
* style placeholders for the ldap attributes. For example, if you wanted to map the LDAP
* attribute <code>displayName</code> to the vcard element <code>FN</code>, the xml
* nippet would be:</p><br><pre>&lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;</pre>
* <p>
* The vCard XML must be escaped in CDATA and must also be well formed. It is the exact
* XML this provider will send to a client after after stripping <code>attr</code> attributes
* and populating the placeholders with the data retrieved from LDAP. This system should
* be flexible enough to handle any client's vCard format. An example mapping follows.<br>
* </p>
* <tt>ldap.vcard-mapping =
* &lt;![CDATA[
* &lt;vCard xmlns='vcard-temp'&gt;
* &lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;
* &lt;NICKNAME attrs=&quot;uid&quot;&gt;{0}&lt;/NICKNAME&gt;
* &lt;BDAY attrs=&quot;dob&quot;&gt;{0}&lt;/BDAY&gt;
* &lt;ADR&gt;
* &lt;HOME/&gt;
* &lt;EXTADR&gt;Ste 500&lt;/EXTADR&gt;
* &lt;STREET&gt;317 SW Alder St&lt;/STREET&gt;
* &lt;LOCALITY&gt;Portland&lt;/LOCALITY&gt;
* &lt;REGION&gt;Oregon&lt;/REGION&gt;
* &lt;PCODE&gt;97204&lt;/PCODE&gt;
* &lt;CTRY&gt;USA&lt;/CTRY&gt;
* &lt;/ADR&gt;
* &lt;TEL&gt;
* &lt;HOME/&gt;
* &lt;VOICE/&gt;
* &lt;NUMBER attrs=&quot;telephoneNumber&quot;&gt;{0}&lt;/NUMBER&gt;
* &lt;/TEL&gt;
* &lt;EMAIL&gt;
* &lt;INTERNET/&gt;
* &lt;USERID attrs=&quot;mail&quot;&gt;{0}&lt;/USERID&gt;
* &lt;/EMAIL&gt;
* &lt;TITLE attrs=&quot;title&quot;&gt;{0}&lt;/TITLE&gt;
* &lt;ROLE attrs=&quot;&quot;&gt;{0}&lt;/ROLE&gt;
* &lt;ORG&gt;
* &lt;ORGNAME attrs=&quot;o&quot;&gt;{0}&lt;/ORGNAME&gt;
* &lt;ORGUNIT attrs=&quot;&quot;&gt;{0}&lt;/ORGUNIT&gt;
* &lt;/ORG&gt;
* &lt;URL attrs=&quot;labeledURI&quot;&gt;{0}&lt;/URL&gt;
* &lt;DESC attrs=&quot;uidNumber,homeDirectory,loginShell&quot;&gt;
* uid: {0} home: {1} shell: {2}
* &lt;/DESC&gt;
* &lt;/vCard&gt;
* ]]&gt;
* </tt>
* <p>
* An easy way to get the vcard format your client needs, assuming you've been
* using the database store, is to do a <code>SELECT value FROM ofVCard WHERE
* username='some_user'</code> in your favorite sql querier and paste the result
* into the <code>vcard-mapping</code> (don't forget the CDATA).</p>
*
* @author rkelly
*/
public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(LdapVCardProvider.class);
private LdapManager manager;
private VCardTemplate template;
private Boolean dbStorageEnabled = false;
/**
* The default vCard provider is used to handle the vCard in the database. vCard
* fields that can be overriden are stored in the database.
*
* This is used/created only if we are storing avatars in the database.
*/
private DefaultVCardProvider defaultProvider = null;
public LdapVCardProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("ldap.vcard-mapping");
manager = LdapManager.getInstance();
initTemplate();
// Listen to property events so that the template is always up to date
PropertyEventDispatcher.addListener(this);
// DB vcard provider used for loading properties overwritten in the DB
defaultProvider = new DefaultVCardProvider();
// Check of avatars can be overwritten (and stored in the database)
dbStorageEnabled = JiveGlobals.getBooleanProperty("ldap.override.avatar", false);
}
/**
* Initializes the VCard template as set by the administrator.
*/
private void initTemplate() {
String property = JiveGlobals.getProperty("ldap.vcard-mapping");
Log.debug("LdapVCardProvider: Found vcard mapping: '" + property);
try {
// Remove CDATA wrapping element
if (property.startsWith("<![CDATA[")) {
property = property.substring(9, property.length()-3);
}
Document document = DocumentHelper.parseText(property);
template = new VCardTemplate(document);
}
catch (Exception e) {
Log.error("Error loading vcard mapping: " + e.getMessage());
}
Log.debug("LdapVCardProvider: attributes size==" + template.getAttributes().length);
}
/**
* Creates a mapping of requested LDAP attributes to their values for the given user.
*
* @param username User we are looking up in LDAP.
* @return Map of LDAP attribute to setting.
*/
private Map<String, String> getLdapAttributes(String username) {
// Un-escape username
username = JID.unescapeNode(username);
Map<String, String> map = new HashMap<>();
DirContext ctx = null;
try {
String userDN = manager.findUserDN(username);
ctx = manager.getContext(manager.getUsersBaseDN(username));
Attributes attrs = ctx.getAttributes(userDN, template.getAttributes());
for (String attribute : template.getAttributes()) {
javax.naming.directory.Attribute attr = attrs.get(attribute);
String value;
if (attr == null) {
Log.debug("LdapVCardProvider: No ldap value found for attribute '" + attribute + "'");
value = "";
}
else {
Object ob = attrs.get(attribute).get();
Log.debug("LdapVCardProvider: Found attribute "+attribute+" of type: "+ob.getClass());
if(ob instanceof String) {
value = (String)ob;
} else {
value = Base64.encodeBytes((byte[])ob);
}
}
Log.debug("LdapVCardProvider: Ldap attribute '" + attribute + "'=>'" + value + "'");
map.put(attribute, value);
}
return map;
}
catch (Exception e) {
Log.error(e.getMessage(), e);
return Collections.emptyMap();
}
finally {
try {
if (ctx != null) {
ctx.close();
}
}
catch (Exception e) {
// Ignore.
}
}
}
/**
* Loads the avatar from LDAP, based off the vcard template.
*
* If enabled, will replace a blank PHOTO element with one from a DB stored vcard.
*
* @param username User we are loading the vcard for.
* @return The loaded vcard element, or null if none found.
*/
@Override
public Element loadVCard(String username) {
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
Log.debug("LdapVCardProvider: Getting mapped vcard for " + username);
Element vcard = new VCard(template).getVCard(map);
// If we have a vcard from ldap, but it doesn't have an avatar filled in, then we
// may fill it with a locally stored vcard element.
if (dbStorageEnabled && vcard != null && (vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*"))) {
Element avatarElement = loadAvatarFromDatabase(username);
if (avatarElement != null) {
Log.debug("LdapVCardProvider: Adding avatar element from local storage");
Element currentElement = vcard.element("PHOTO");
if (currentElement != null) {
vcard.remove(currentElement);
}
vcard.add(avatarElement);
}
}
Log.debug("LdapVCardProvider: Returning vcard");
return vcard;
}
/**
* Returns a merged LDAP vCard combined with a PHOTO element provided in specified vCard.
*
* @param username User whose vCard this is.
* @param mergeVCard vCard element that we are merging PHOTO element from into the LDAP vCard.
* @return vCard element after merging in PHOTO element to LDAP data.
*/
private Element getMergedVCard(String username, Element mergeVCard) {
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
Log.debug("LdapVCardProvider: Retrieving LDAP mapped vcard for " + username);
if (map.isEmpty()) {
return null;
}
Element vcard = new VCard(template).getVCard(map);
if (mergeVCard == null) {
// No vcard passed in? Hrm. Fine, return LDAP vcard.
return vcard;
}
if (mergeVCard.element("PHOTO") == null) {
// Merged vcard has no photo element, return LDAP vcard as is.
return vcard;
}
Element photoElement = mergeVCard.element("PHOTO").createCopy();
if (photoElement == null || photoElement.element("BINVAL") == null || photoElement.element("BINVAL").getText().matches("\\s*")) {
// We were passed something null or empty, so lets just return the LDAP based vcard.
return vcard;
}
// Now we need to check that the LDAP vcard doesn't have a PHOTO element that's filled in.
if (!((vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*")))) {
// Hrm, it does, return the original vcard;
return vcard;
}
Log.debug("LdapVCardProvider: Merging avatar element from passed vcard");
Element currentElement = vcard.element("PHOTO");
if (currentElement != null) {
vcard.remove(currentElement);
}
vcard.add(photoElement);
return vcard;
}
/**
* Loads the avatar element from the user's DB stored vcard.
*
* @param username User whose vcard/avatar element we are loading.
* @return Loaded avatar element or null if not found.
*/
private Element loadAvatarFromDatabase(String username) {
Element vcardElement = defaultProvider.loadVCard(username);
Element avatarElement = null;
if (vcardElement != null && vcardElement.element("PHOTO") != null) {
avatarElement = vcardElement.element("PHOTO").createCopy();
}
return avatarElement;
}
/**
* Handles when a user creates a new vcard.
*
* @param username User that created a new vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public Element createVCard(String username, Element vCardElement)
throws UnsupportedOperationException, AlreadyExistsException {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
/**
* Handles when a user updates their vcard.
*
* @param username User that updated their vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public Element updateVCard(String username, Element vCardElement) throws UnsupportedOperationException {
if (dbStorageEnabled && defaultProvider != null) {
if (isValidVCardChange(username, vCardElement)) {
Element mergedVCard = getMergedVCard(username, vCardElement);
try {
defaultProvider.updateVCard(username, mergedVCard);
} catch (NotFoundException e) {
try {
defaultProvider.createVCard(username, mergedVCard);
} catch (AlreadyExistsException e1) {
// Ignore
}
}
return mergedVCard;
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes.");
}
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
}
/**
* Handles when a user deletes their vcard.
*
* @param username User that deketed their vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public void deleteVCard(String username) throws UnsupportedOperationException {
throw new UnsupportedOperationException("LdapVCardProvider: Attempted to delete vcard in read-only mode.");
}
/**
* Returns true or false if the change to the existing vcard is valid (only to PHOTO element)
*
* @param username User who's LDAP-based vcard we will compare with.
* @param newvCard New vCard Element we will compare against.
* @return True or false if the changes made were valid (only to PHOTO element)
*/
private Boolean isValidVCardChange(String username, Element newvCard) {
if (newvCard == null) {
// Well if there's nothing to change, of course it's valid.
Log.debug("LdapVCardProvider: No new vcard provided (no changes), accepting.");
return true;
}
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
// Retrieve LDAP created vcard for comparison
Element ldapvCard = new VCard(template).getVCard(map);
if (ldapvCard == null) {
// This person has no vcard at all, may not change it!
Log.debug("LdapVCardProvider: User has no LDAP vcard, nothing they can change, rejecting.");
return false;
}
// If the LDAP vcard has a non-empty PHOTO element set, then there is literally no way this will be accepted.
Element ldapPhotoElem = ldapvCard.element("PHOTO");
if (ldapPhotoElem != null) {
Element ldapBinvalElem = ldapPhotoElem.element("BINVAL");
if (ldapBinvalElem != null && !ldapBinvalElem.getTextTrim().matches("\\s*")) {
// LDAP is providing a valid PHOTO element, byebye!
Log.debug("LdapVCardProvider: LDAP has a PHOTO element set, no way to override, rejecting.");
return false;
}
}
// Retrieve database vcard, if it exists
Element dbvCard = defaultProvider.loadVCard(username);
if (dbvCard != null) {
Element dbPhotoElem = dbvCard.element("PHOTO");
if (dbPhotoElem == null) {
// DB has no photo, lets accept what we got.
Log.debug("LdapVCardProvider: Database has no PHOTO element, accepting update.");
return true;
}
else {
Element newPhotoElem = newvCard.element("PHOTO");
if (newPhotoElem == null) {
Log.debug("LdapVCardProvider: Photo element was removed, accepting update.");
return true;
}
// Note: NodeComparator never seems to consider these equal, even if they are?
if (!dbPhotoElem.asXML().equals(newPhotoElem.asXML())) {
// Photo element was changed. Ignore all other changes and accept this.
Log.debug("LdapVCardProvider: PHOTO element changed, accepting update.");
return true;
}
}
}
else {
// No vcard exists in database
Log.debug("LdapVCardProvider: Database has no vCard stored, accepting update.");
return true;
}
// Ok, either something bad changed or nothing changed. Either way, user either:
// 1. should not have tried to change something 'readonly'
// 2. shouldn't have bothered submitting no changes
// So we'll consider this a bad return.
Log.debug("LdapVCardProvider: PHOTO element didn't change, no reason to accept this, rejecting.");
return false;
}
@Override
public boolean isReadOnly() {
return !dbStorageEnabled;
}
@Override
public void propertySet(String property, Map params) {
if ("ldap.override.avatar".equals(property)) {
dbStorageEnabled = Boolean.parseBoolean((String)params.get("value"));
}
else if ("ldap.vcard-mapping".equals(property)) {
initTemplate();
// Reset cache of vCards
VCardManager.getInstance().reset();
}
}
@Override
public void propertyDeleted(String property, Map params) {
if ("ldap.override.avatar".equals(property)) {
dbStorageEnabled = false;
}
}
@Override
public void xmlPropertySet(String property, Map params) {
//Ignore
}
@Override
public void xmlPropertyDeleted(String property, Map params) {
//Ignore
}
/**
* Class to hold a <code>Document</code> representation of a vcard mapping
* and unique attribute placeholders. Used by <code>VCard</code> to apply
* a <code>Map</code> of ldap attributes to ldap values via
* <code>MessageFormat</code>
*
* @author rkelly
*/
private static class VCardTemplate {
private Document document;
private String[] attributes;
public VCardTemplate(Document document) {
Set<String> set = new HashSet<>();
this.document = document;
treeWalk(this.document.getRootElement(), set);
attributes = set.toArray(new String[set.size()]);
}
public String[] getAttributes() {
return attributes;
}
public Document getDocument() {
return document;
}
private void treeWalk(Element element, Set<String> set) {
for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
if (node instanceof Element) {
Element emement = (Element) node;
StringTokenizer st = new StringTokenizer(emement.getTextTrim(), ", //{}");
while (st.hasMoreTokens()) {
// Remove enclosing {}
String string = st.nextToken().replaceAll("(\\{)([\\d\\D&&[^}]]+)(})", "$2");
Log.debug("VCardTemplate: found attribute " + string);
set.add(string);
}
treeWalk(emement, set);
}
}
}
}
/**
* vCard class that converts vcard data using a template.
*/
private static class VCard {
private VCardTemplate template;
public VCard(VCardTemplate template) {
this.template = template;
}
public Element getVCard(Map<String, String> map) {
Document document = (Document) template.getDocument().clone();
Element element = document.getRootElement();
return treeWalk(element, map);
}
private Element treeWalk(Element element, Map<String, String> map) {
for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
if (node instanceof Element) {
Element emement = (Element) node;
String elementText = emement.getTextTrim();
if (elementText != null && !"".equals(elementText)) {
String format = emement.getStringValue();
StringTokenizer st = new StringTokenizer(elementText, ", //{}");
while (st.hasMoreTokens()) {
// Remove enclosing {}
String field = st.nextToken();
String attrib = field.replaceAll("(\\{)(" + field + ")(})", "$2");
String value = map.get(attrib);
format = format.replaceFirst("(\\{)(" + field + ")(})", Matcher.quoteReplacement(value));
}
emement.setText(format);
}
treeWalk(emement, map);
}
}
return element;
}
}
}
/*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* 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.jivesoftware.openfire.ldap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.jivesoftware.openfire.vcard.DefaultVCardProvider;
import org.jivesoftware.openfire.vcard.PhotoResizer;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.Base64;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Read-only LDAP provider for vCards.Configuration consists of adding a provider:
* <p>
* <tt>provider.vcard.className = org.jivesoftware.openfire.ldap.LdapVCardProvider</tt>
* </p>
* <p>and an xml vcard-mapping in the system properties.</p>
* <p>
* The vcard attributes can be configured by adding an <code>attrs="attr1,attr2"</code>
* attribute to the vcard elements.</p>
* <p>
* Arbitrary text can be used for the element values as well as <code>MessageFormat</code>
* style placeholders for the ldap attributes. For example, if you wanted to map the LDAP
* attribute <code>displayName</code> to the vcard element <code>FN</code>, the xml
* nippet would be:</p><br><pre>&lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;</pre>
* <p>
* The vCard XML must be escaped in CDATA and must also be well formed. It is the exact
* XML this provider will send to a client after after stripping <code>attr</code> attributes
* and populating the placeholders with the data retrieved from LDAP. This system should
* be flexible enough to handle any client's vCard format. An example mapping follows.<br>
* </p>
* <tt>ldap.vcard-mapping =
* &lt;![CDATA[
* &lt;vCard xmlns='vcard-temp'&gt;
* &lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;
* &lt;NICKNAME attrs=&quot;uid&quot;&gt;{0}&lt;/NICKNAME&gt;
* &lt;BDAY attrs=&quot;dob&quot;&gt;{0}&lt;/BDAY&gt;
* &lt;ADR&gt;
* &lt;HOME/&gt;
* &lt;EXTADR&gt;Ste 500&lt;/EXTADR&gt;
* &lt;STREET&gt;317 SW Alder St&lt;/STREET&gt;
* &lt;LOCALITY&gt;Portland&lt;/LOCALITY&gt;
* &lt;REGION&gt;Oregon&lt;/REGION&gt;
* &lt;PCODE&gt;97204&lt;/PCODE&gt;
* &lt;CTRY&gt;USA&lt;/CTRY&gt;
* &lt;/ADR&gt;
* &lt;TEL&gt;
* &lt;HOME/&gt;
* &lt;VOICE/&gt;
* &lt;NUMBER attrs=&quot;telephoneNumber&quot;&gt;{0}&lt;/NUMBER&gt;
* &lt;/TEL&gt;
* &lt;EMAIL&gt;
* &lt;INTERNET/&gt;
* &lt;USERID attrs=&quot;mail&quot;&gt;{0}&lt;/USERID&gt;
* &lt;/EMAIL&gt;
* &lt;TITLE attrs=&quot;title&quot;&gt;{0}&lt;/TITLE&gt;
* &lt;ROLE attrs=&quot;&quot;&gt;{0}&lt;/ROLE&gt;
* &lt;ORG&gt;
* &lt;ORGNAME attrs=&quot;o&quot;&gt;{0}&lt;/ORGNAME&gt;
* &lt;ORGUNIT attrs=&quot;&quot;&gt;{0}&lt;/ORGUNIT&gt;
* &lt;/ORG&gt;
* &lt;URL attrs=&quot;labeledURI&quot;&gt;{0}&lt;/URL&gt;
* &lt;DESC attrs=&quot;uidNumber,homeDirectory,loginShell&quot;&gt;
* uid: {0} home: {1} shell: {2}
* &lt;/DESC&gt;
* &lt;/vCard&gt;
* ]]&gt;
* </tt>
* <p>
* An easy way to get the vcard format your client needs, assuming you've been
* using the database store, is to do a <code>SELECT value FROM ofVCard WHERE
* username='some_user'</code> in your favorite sql querier and paste the result
* into the <code>vcard-mapping</code> (don't forget the CDATA).</p>
*
* @author rkelly
*/
public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(LdapVCardProvider.class);
private LdapManager manager;
private VCardTemplate template;
private Boolean dbStorageEnabled = false;
/**
* The default vCard provider is used to handle the vCard in the database. vCard
* fields that can be overriden are stored in the database.
*
* This is used/created only if we are storing avatars in the database.
*/
private DefaultVCardProvider defaultProvider = null;
public LdapVCardProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("ldap.vcard-mapping");
manager = LdapManager.getInstance();
initTemplate();
// Listen to property events so that the template is always up to date
PropertyEventDispatcher.addListener(this);
// DB vcard provider used for loading properties overwritten in the DB
defaultProvider = new DefaultVCardProvider();
// Check of avatars can be overwritten (and stored in the database)
dbStorageEnabled = JiveGlobals.getBooleanProperty("ldap.override.avatar", false);
}
/**
* Initializes the VCard template as set by the administrator.
*/
private void initTemplate() {
String property = JiveGlobals.getProperty("ldap.vcard-mapping");
Log.debug("LdapVCardProvider: Found vcard mapping: '" + property);
try {
// Remove CDATA wrapping element
if (property.startsWith("<![CDATA[")) {
property = property.substring(9, property.length()-3);
}
Document document = DocumentHelper.parseText(property);
template = new VCardTemplate(document);
}
catch (Exception e) {
Log.error("Error loading vcard mapping: " + e.getMessage());
}
Log.debug("LdapVCardProvider: attributes size==" + template.getAttributes().length);
}
/**
* Creates a mapping of requested LDAP attributes to their values for the given user.
*
* @param username User we are looking up in LDAP.
* @return Map of LDAP attribute to setting.
*/
private Map<String, String> getLdapAttributes(String username) {
// Un-escape username
username = JID.unescapeNode(username);
Map<String, String> map = new HashMap<>();
DirContext ctx = null;
try {
String userDN = manager.findUserDN(username);
ctx = manager.getContext(manager.getUsersBaseDN(username));
Attributes attrs = ctx.getAttributes(userDN, template.getAttributes());
for (String attribute : template.getAttributes()) {
javax.naming.directory.Attribute attr = attrs.get(attribute);
String value;
if (attr == null) {
Log.debug("LdapVCardProvider: No ldap value found for attribute '" + attribute + "'");
value = "";
}
else {
Object ob = attrs.get(attribute).get();
Log.debug("LdapVCardProvider: Found attribute "+attribute+" of type: "+ob.getClass());
if(ob instanceof String) {
value = (String)ob;
} else {
value = Base64.encodeBytes((byte[])ob);
}
}
Log.debug("LdapVCardProvider: Ldap attribute '" + attribute + "'=>'" + value + "'");
map.put(attribute, value);
}
return map;
}
catch (Exception e) {
Log.error(e.getMessage(), e);
return Collections.emptyMap();
}
finally {
try {
if (ctx != null) {
ctx.close();
}
}
catch (Exception e) {
// Ignore.
}
}
}
/**
* Loads the avatar from LDAP, based off the vcard template.
*
* If enabled, will replace a blank PHOTO element with one from a DB stored vcard.
*
* @param username User we are loading the vcard for.
* @return The loaded vcard element, or null if none found.
*/
@Override
public Element loadVCard(String username) {
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
Log.debug("LdapVCardProvider: Getting mapped vcard for " + username);
Element vcard = new VCard(template).getVCard(map);
// If we have a vcard from ldap, but it doesn't have an avatar filled in, then we
// may fill it with a locally stored vcard element.
if (dbStorageEnabled && vcard != null && (vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*"))) {
Element avatarElement = loadAvatarFromDatabase(username);
if (avatarElement != null) {
Log.debug("LdapVCardProvider: Adding avatar element from local storage");
Element currentElement = vcard.element("PHOTO");
if (currentElement != null) {
vcard.remove(currentElement);
}
vcard.add(avatarElement);
}
}
if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_LOAD, PhotoResizer.PROPERTY_RESIZE_ON_LOAD_DEFAULT ) )
{
PhotoResizer.resizeAvatar( vcard );
}
Log.debug("LdapVCardProvider: Returning vcard");
return vcard;
}
/**
* Returns a merged LDAP vCard combined with a PHOTO element provided in specified vCard.
*
* @param username User whose vCard this is.
* @param mergeVCard vCard element that we are merging PHOTO element from into the LDAP vCard.
* @return vCard element after merging in PHOTO element to LDAP data.
*/
private Element getMergedVCard(String username, Element mergeVCard) {
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
Log.debug("LdapVCardProvider: Retrieving LDAP mapped vcard for " + username);
if (map.isEmpty()) {
return null;
}
Element vcard = new VCard(template).getVCard(map);
if (mergeVCard == null) {
// No vcard passed in? Hrm. Fine, return LDAP vcard.
return vcard;
}
if (mergeVCard.element("PHOTO") == null) {
// Merged vcard has no photo element, return LDAP vcard as is.
return vcard;
}
Element photoElement = mergeVCard.element("PHOTO").createCopy();
if (photoElement == null || photoElement.element("BINVAL") == null || photoElement.element("BINVAL").getText().matches("\\s*")) {
// We were passed something null or empty, so lets just return the LDAP based vcard.
return vcard;
}
// Now we need to check that the LDAP vcard doesn't have a PHOTO element that's filled in.
if (!((vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*")))) {
// Hrm, it does, return the original vcard;
return vcard;
}
Log.debug("LdapVCardProvider: Merging avatar element from passed vcard");
Element currentElement = vcard.element("PHOTO");
if (currentElement != null) {
vcard.remove(currentElement);
}
vcard.add(photoElement);
return vcard;
}
/**
* Loads the avatar element from the user's DB stored vcard.
*
* @param username User whose vcard/avatar element we are loading.
* @return Loaded avatar element or null if not found.
*/
private Element loadAvatarFromDatabase(String username) {
Element vcardElement = defaultProvider.loadVCard(username);
Element avatarElement = null;
if (vcardElement != null && vcardElement.element("PHOTO") != null) {
avatarElement = vcardElement.element("PHOTO").createCopy();
}
return avatarElement;
}
/**
* Handles when a user creates a new vcard.
*
* @param username User that created a new vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public Element createVCard(String username, Element vCardElement)
throws UnsupportedOperationException, AlreadyExistsException {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
/**
* Handles when a user updates their vcard.
*
* @param username User that updated their vcard.
* @param vCardElement vCard element containing the new vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public Element updateVCard(String username, Element vCardElement) throws UnsupportedOperationException {
if (dbStorageEnabled && defaultProvider != null) {
if (isValidVCardChange(username, vCardElement)) {
Element mergedVCard = getMergedVCard(username, vCardElement);
try {
defaultProvider.updateVCard(username, mergedVCard);
} catch (NotFoundException e) {
try {
defaultProvider.createVCard(username, mergedVCard);
} catch (AlreadyExistsException e1) {
// Ignore
}
}
return mergedVCard;
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes.");
}
}
else {
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
}
}
/**
* Handles when a user deletes their vcard.
*
* @param username User that deketed their vcard.
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
*/
@Override
public void deleteVCard(String username) throws UnsupportedOperationException {
throw new UnsupportedOperationException("LdapVCardProvider: Attempted to delete vcard in read-only mode.");
}
/**
* Returns true or false if the change to the existing vcard is valid (only to PHOTO element)
*
* @param username User who's LDAP-based vcard we will compare with.
* @param newvCard New vCard Element we will compare against.
* @return True or false if the changes made were valid (only to PHOTO element)
*/
private Boolean isValidVCardChange(String username, Element newvCard) {
if (newvCard == null) {
// Well if there's nothing to change, of course it's valid.
Log.debug("LdapVCardProvider: No new vcard provided (no changes), accepting.");
return true;
}
// Un-escape username.
username = JID.unescapeNode(username);
Map<String, String> map = getLdapAttributes(username);
// Retrieve LDAP created vcard for comparison
Element ldapvCard = new VCard(template).getVCard(map);
if (ldapvCard == null) {
// This person has no vcard at all, may not change it!
Log.debug("LdapVCardProvider: User has no LDAP vcard, nothing they can change, rejecting.");
return false;
}
// If the LDAP vcard has a non-empty PHOTO element set, then there is literally no way this will be accepted.
Element ldapPhotoElem = ldapvCard.element("PHOTO");
if (ldapPhotoElem != null) {
Element ldapBinvalElem = ldapPhotoElem.element("BINVAL");
if (ldapBinvalElem != null && !ldapBinvalElem.getTextTrim().matches("\\s*")) {
// LDAP is providing a valid PHOTO element, byebye!
Log.debug("LdapVCardProvider: LDAP has a PHOTO element set, no way to override, rejecting.");
return false;
}
}
// Retrieve database vcard, if it exists
Element dbvCard = defaultProvider.loadVCard(username);
if (dbvCard != null) {
Element dbPhotoElem = dbvCard.element("PHOTO");
if (dbPhotoElem == null) {
// DB has no photo, lets accept what we got.
Log.debug("LdapVCardProvider: Database has no PHOTO element, accepting update.");
return true;
}
else {
Element newPhotoElem = newvCard.element("PHOTO");
if (newPhotoElem == null) {
Log.debug("LdapVCardProvider: Photo element was removed, accepting update.");
return true;
}
// Note: NodeComparator never seems to consider these equal, even if they are?
if (!dbPhotoElem.asXML().equals(newPhotoElem.asXML())) {
// Photo element was changed. Ignore all other changes and accept this.
Log.debug("LdapVCardProvider: PHOTO element changed, accepting update.");
return true;
}
}
}
else {
// No vcard exists in database
Log.debug("LdapVCardProvider: Database has no vCard stored, accepting update.");
return true;
}
// Ok, either something bad changed or nothing changed. Either way, user either:
// 1. should not have tried to change something 'readonly'
// 2. shouldn't have bothered submitting no changes
// So we'll consider this a bad return.
Log.debug("LdapVCardProvider: PHOTO element didn't change, no reason to accept this, rejecting.");
return false;
}
@Override
public boolean isReadOnly() {
return !dbStorageEnabled;
}
@Override
public void propertySet(String property, Map params) {
if ("ldap.override.avatar".equals(property)) {
dbStorageEnabled = Boolean.parseBoolean((String)params.get("value"));
}
else if ("ldap.vcard-mapping".equals(property)) {
initTemplate();
// Reset cache of vCards
VCardManager.getInstance().reset();
}
}
@Override
public void propertyDeleted(String property, Map params) {
if ("ldap.override.avatar".equals(property)) {
dbStorageEnabled = false;
}
}
@Override
public void xmlPropertySet(String property, Map params) {
//Ignore
}
@Override
public void xmlPropertyDeleted(String property, Map params) {
//Ignore
}
/**
* Class to hold a <code>Document</code> representation of a vcard mapping
* and unique attribute placeholders. Used by <code>VCard</code> to apply
* a <code>Map</code> of ldap attributes to ldap values via
* <code>MessageFormat</code>
*
* @author rkelly
*/
private static class VCardTemplate {
private Document document;
private String[] attributes;
public VCardTemplate(Document document) {
Set<String> set = new HashSet<>();
this.document = document;
treeWalk(this.document.getRootElement(), set);
attributes = set.toArray(new String[set.size()]);
}
public String[] getAttributes() {
return attributes;
}
public Document getDocument() {
return document;
}
private void treeWalk(Element element, Set<String> set) {
for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
if (node instanceof Element) {
Element emement = (Element) node;
StringTokenizer st = new StringTokenizer(emement.getTextTrim(), ", //{}");
while (st.hasMoreTokens()) {
// Remove enclosing {}
String string = st.nextToken().replaceAll("(\\{)([\\d\\D&&[^}]]+)(})", "$2");
Log.debug("VCardTemplate: found attribute " + string);
set.add(string);
}
treeWalk(emement, set);
}
}
}
}
/**
* vCard class that converts vcard data using a template.
*/
private static class VCard {
private VCardTemplate template;
public VCard(VCardTemplate template) {
this.template = template;
}
public Element getVCard(Map<String, String> map) {
Document document = (Document) template.getDocument().clone();
Element element = document.getRootElement();
return treeWalk(element, map);
}
private Element treeWalk(Element element, Map<String, String> map) {
for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
if (node instanceof Element) {
Element emement = (Element) node;
String elementText = emement.getTextTrim();
if (elementText != null && !"".equals(elementText)) {
String format = emement.getStringValue();
StringTokenizer st = new StringTokenizer(elementText, ", //{}");
while (st.hasMoreTokens()) {
// Remove enclosing {}
String field = st.nextToken();
String attrib = field.replaceAll("(\\{)(" + field + ")(})", "$2");
String value = map.get(attrib);
format = format.replaceFirst("(\\{)(" + field + ")(})", Matcher.quoteReplacement(value));
}
emement.setText(format);
}
treeWalk(emement, map);
}
}
return element;
}
}
}
......@@ -28,6 +28,7 @@ import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -98,6 +99,12 @@ public class DefaultVCardProvider implements VCardProvider {
}
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_LOAD, PhotoResizer.PROPERTY_RESIZE_ON_LOAD_DEFAULT ) )
{
PhotoResizer.resizeAvatar( vCardElement );
}
return vCardElement;
}
}
......@@ -109,6 +116,11 @@ public class DefaultVCardProvider implements VCardProvider {
throw new AlreadyExistsException("Username " + username + " already has a vCard");
}
if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_CREATE, PhotoResizer.PROPERTY_RESIZE_ON_CREATE_DEFAULT ) )
{
PhotoResizer.resizeAvatar( vCardElement );
}
Connection con = null;
PreparedStatement pstmt = null;
try {
......@@ -133,6 +145,12 @@ public class DefaultVCardProvider implements VCardProvider {
// The user already has a vCard
throw new NotFoundException("Username " + username + " does not have a vCard");
}
if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_CREATE, PhotoResizer.PROPERTY_RESIZE_ON_CREATE_DEFAULT ) )
{
PhotoResizer.resizeAvatar( vCardElement );
}
Connection con = null;
PreparedStatement pstmt = null;
try {
......
package org.igniterealtime.openfire.plugin.avatarresizer;
package org.jivesoftware.openfire.vcard;
import org.dom4j.Element;
import org.jivesoftware.util.Base64;
......@@ -21,9 +21,22 @@ import java.util.Iterator;
/**
* Image resizing utility methods.
*/
public class Resizer
public class PhotoResizer
{
private static final Logger Log = LoggerFactory.getLogger( Resizer.class );
private static final Logger Log = LoggerFactory.getLogger( PhotoResizer.class );
// Property that, when 'true' causes avatars that are being loaded from backend storage to be resized, prior to be
// processed and send to entities.
public static final String PROPERTY_RESIZE_ON_LOAD = "avatar.resize.enable-on-load";
public static final boolean PROPERTY_RESIZE_ON_LOAD_DEFAULT = true;
// Property that, when 'true' causes avatars that are being stored in backend storage to be resized.
public static final String PROPERTY_RESIZE_ON_CREATE = "avatar.resize.enable-on-create";
public static final boolean PROPERTY_RESIZE_ON_CREATE_DEFAULT = false;
// Property that controls the target dimension, in pixels.
public static final String PROPERTY_TARGETDIMENSION = "avatar.resize.targetdimension";
public static final int PROPERTY_TARGETDIMENSION_DEFAULT = 96;
public static void resizeAvatar( final Element vCardElement )
{
......@@ -63,7 +76,7 @@ public class Resizer
final byte[] original = Base64.decode( element.getTextTrim() );
// Crop and shrink, if needed.
final int targetDimension = JiveGlobals.getIntProperty( "avatar.resize.targetdimension", 96 );
final int targetDimension = JiveGlobals.getIntProperty( PROPERTY_TARGETDIMENSION, PROPERTY_TARGETDIMENSION_DEFAULT );
final byte[] resized = cropAndShrink( original, targetDimension, iw );
// If a resized image was created, replace to original avatar in the VCard.
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Avatar Resizer Plugin Changelog</title>
<style type="text/css">
BODY {
font-size: 100%;
}
BODY, TD, TH {
font-family: tahoma, verdana, arial, helvetica, sans-serif;
font-size: 0.8em;
}
H2 {
font-size: 10pt;
font-weight: bold;
padding-left: 1em;
}
A:hover {
text-decoration: none;
}
H1 {
font-family: tahoma, arial, helvetica, sans-serif;
font-size: 1.4em;
font-weight: bold;
border-bottom: 1px #ccc solid;
padding-bottom: 2px;
}
TT {
font-family: courier new;
font-weight: bold;
color: #060;
}
PRE {
font-family: courier new;
font-size: 100%;
}
</style>
</head>
<body>
<h1>
Avatar Resizer Plugin Changelog
</h1>
<p><b>1.0.1</b> -- April 14, 2016</p>
<ul>
<li>Added proper documentation.</li>
</ul>
<p><b>1.0.0</b> -- April 12, 2016</p>
<ul>
<li>First stab at porting the original code of Aaron Sierra. Distributed as a proof-of-concept in the <a
href="https://community.igniterealtime.org/message/256783">IgniteRealtime community</a>.
</li>
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.igniterealtime.openfire.plugin.avatarresizer.AvatarResizerPlugin</class>
<name>Avatar Resizer</name>
<description>Ensures vCard-based avatars are not to large for comfort.</description>
<author>Guus der Kinderen</author>
<version>1.0.1</version>
<date>4/14/2016</date>
</plugin>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>4.2.0-SNAPSHOT</version>
</parent>
<groupId>org.igniterealtime.openfire.plugins</groupId>
<artifactId>avatarResizer</artifactId>
<version>1.0.1</version>
<name>Avatar Resizer Plugin</name>
<description>Ensures vCard-based avatars are not to large for comfort.</description>
<developers>
<developer>
<id>guusdk</id>
<name>Guus der Kinderen</name>
<email>guus.der.kinderen@gmail.com</email>
<organization>Ignite Realtime</organization>
<organizationUrl>https://www.igniterealtime.org</organizationUrl>
</developer>
</developers>
<build>
<sourceDirectory>src/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Avatar Resizer Plugin Readme</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
text-align: left;
}
CAPTION {
font-size: 0.7em;
margin-top: 1.2em;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
</style>
</head>
<body>
<h1>
Avatar Resizer Plugin Readme
</h1>
<h2>Overview</h2>
<p>
The avatar resizer plugin will scale down VCard-based avatars, when the corresponding vCard is stored in Openfire
(it will not affect avatars that are being transferred between end-users).
</p>
<p>
Using avatars that are large can introduce problems for clients - not only will all of the data need to be
transferred (which is done with inefficient base64-encoding), some clients have trouble displaying large sets. RAM
and CPU spikes can occur as a result.
</p>
<p>
To prevent these problems, this plugin scales Avatars from vCards, but only if the vCards are provided by Openfire
itself (through one of its <a href="https://www.igniterealtime.org/builds/openfire/docs/latest/documentation/javadoc/org/jivesoftware/openfire/vcard/VCardProvider.html">VCardProvider</a>
implementations, which include the default, database-oriented provider, an LDAP provider and a Atlassian Crowd
provider).
</p>
<p>
Avatar scaling of this plugin is in accordance with the guidelines listed in
<a href="https://www.xmpp.org/extensions/xep-0153.html#bizrules-image">XEP&#8209;0153: vCard-Based Avatars</a>.
Specifically, this plugin shrinks and crops the avatar to a 96 by 96 pixel square image (although the size is
configurable, see below).
</p>
<h2>Installation</h2>
<p>
Copy avatarResizer.jar into the plugins directory of your Openfire installation. The plugin will then be
automatically deployed. To upgrade to a new version, copy the new avatarResizer.jar file over the existing file.
</p>
<h2>Configuration</h2>
<p>
The plugin does not need configuration. Simply installing the plugin will enable it. The properties defined here can
be set to override default behavior of the plugin.
</p>
<table>
<caption>Openfire properties for the Avatar Resizer plugin</caption>
<thead>
<tr><th>Property name</th><th>Default value</th><th>Description</th></tr>
</thead>
<tbody>
<tr>
<td><tt>avatar.resize.targetdimension</tt></td>
<td><pre>96</pre></td>
<td>Size (in pixels) of the resized avatar.</td>
</tr>
</tbody>
</table>
<h2>Credits</h2>
<p>
This plugin is based on an implementation by Aaron Sierra, which was contributed to IgniteRealtime through Mike Ray.
</p>
<p>
Icons made by <a href="http://www.flaticon.com/authors/stephen-hutchings" title="Stephen Hutchings">Stephen Hutchings</a>
from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a>, licensed by
<a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a>.
</p>
</body>
</html>
package org.igniterealtime.openfire.plugin.avatarresizer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.JiveGlobals;
import java.io.File;
/**
* A plugin that intercepts avatars in vCards retrieved from the vCardManager, and re-sizes them when appropriate.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class AvatarResizerPlugin implements Plugin
{
@Override
public void initializePlugin( PluginManager manager, File pluginDirectory )
{
final VCardProvider provider = VCardManager.getProvider();
if ( provider != null && !( provider instanceof DelegateVCardProvider ) )
{
// Setting the property will cause the VCardProvider to re-initialize.
JiveGlobals.setProperty( "provider.vcard.className", DelegateVCardProvider.class.getCanonicalName() );
final DelegateVCardProvider delegateVCardProvider = (DelegateVCardProvider) VCardManager.getProvider();
delegateVCardProvider.setDelegate( provider );
}
}
@Override
public void destroyPlugin()
{
final VCardProvider provider = VCardManager.getProvider();
if ( provider != null && provider instanceof DelegateVCardProvider )
{
final DelegateVCardProvider delegateVCardProvider = (DelegateVCardProvider) provider;
final VCardProvider originalProvider = delegateVCardProvider.getDelegate();
JiveGlobals.setProperty( "provider.vcard.className", originalProvider.getClass().getCanonicalName() );
}
}
}
package org.igniterealtime.openfire.plugin.avatarresizer;
import org.dom4j.Element;
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.NotFoundException;
/**
* A vCard Provider that delegates to another provider, applying image resizing on the results from the delegate.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class DelegateVCardProvider implements VCardProvider
{
private VCardProvider delegate;
public DelegateVCardProvider()
{
}
public VCardProvider getDelegate()
{
return delegate;
}
public void setDelegate( VCardProvider delegate )
{
if ( delegate == null )
{
throw new IllegalArgumentException( "Argument 'delegate' cannot be null." );
}
if ( delegate instanceof DelegateVCardProvider )
{
throw new IllegalArgumentException( "Argument 'delegate' cannot be an instance of DelegateVCardProvider." );
}
this.delegate = delegate;
}
@Override
public Element loadVCard( String username )
{
final Element element = delegate.loadVCard( username );
Resizer.resizeAvatar( element );
return element;
}
@Override
public Element createVCard( String username, Element vCardElement ) throws AlreadyExistsException
{
final Element element = delegate.createVCard( username, vCardElement );
Resizer.resizeAvatar( element );
return element;
}
@Override
public Element updateVCard( String username, Element vCardElement ) throws NotFoundException
{
final Element element = delegate.updateVCard( username, vCardElement );
Resizer.resizeAvatar( element );
return element;
}
@Override
public void deleteVCard( String username )
{
delegate.deleteVCard( username );
}
@Override
public boolean isReadOnly()
{
return delegate.isReadOnly();
}
}
......@@ -9,7 +9,6 @@
<modules>
<module>openfire-plugin-assembly-descriptor</module>
<module>avatarResizer</module>
<module>bookmarks</module>
<module>broadcast</module>
<module>callbackOnOffline</module>
......@@ -199,4 +198,4 @@
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
\ No newline at end of file
</project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment