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. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.ldap; package org.jivesoftware.openfire.ldap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import javax.naming.directory.Attributes; import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext; import javax.naming.directory.DirContext;
import org.dom4j.Document; import org.dom4j.Document;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.Node; import org.dom4j.Node;
import org.jivesoftware.openfire.vcard.DefaultVCardProvider; import org.jivesoftware.openfire.vcard.DefaultVCardProvider;
import org.jivesoftware.openfire.vcard.VCardManager; import org.jivesoftware.openfire.vcard.PhotoResizer;
import org.jivesoftware.openfire.vcard.VCardProvider; import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.AlreadyExistsException; import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.Base64; import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.Base64;
import org.jivesoftware.util.NotFoundException; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.PropertyEventListener; import org.jivesoftware.util.PropertyEventDispatcher;
import org.slf4j.Logger; import org.jivesoftware.util.PropertyEventListener;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.xmpp.packet.JID; import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Read-only LDAP provider for vCards.Configuration consists of adding a provider: /**
* <p> * Read-only LDAP provider for vCards.Configuration consists of adding a provider:
* <tt>provider.vcard.className = org.jivesoftware.openfire.ldap.LdapVCardProvider</tt> * <p>
* </p> * <tt>provider.vcard.className = org.jivesoftware.openfire.ldap.LdapVCardProvider</tt>
* <p>and an xml vcard-mapping in the system properties.</p> * </p>
* <p> * <p>and an xml vcard-mapping in the system properties.</p>
* The vcard attributes can be configured by adding an <code>attrs="attr1,attr2"</code> * <p>
* attribute to the vcard elements.</p> * The vcard attributes can be configured by adding an <code>attrs="attr1,attr2"</code>
* <p> * attribute to the vcard elements.</p>
* Arbitrary text can be used for the element values as well as <code>MessageFormat</code> * <p>
* style placeholders for the ldap attributes. For example, if you wanted to map the LDAP * Arbitrary text can be used for the element values as well as <code>MessageFormat</code>
* attribute <code>displayName</code> to the vcard element <code>FN</code>, the xml * style placeholders for the ldap attributes. For example, if you wanted to map the LDAP
* nippet would be:</p><br><pre>&lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;</pre> * attribute <code>displayName</code> to the vcard element <code>FN</code>, the xml
* <p> * nippet would be:</p><br><pre>&lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;</pre>
* The vCard XML must be escaped in CDATA and must also be well formed. It is the exact * <p>
* XML this provider will send to a client after after stripping <code>attr</code> attributes * The vCard XML must be escaped in CDATA and must also be well formed. It is the exact
* and populating the placeholders with the data retrieved from LDAP. This system should * XML this provider will send to a client after after stripping <code>attr</code> attributes
* be flexible enough to handle any client's vCard format. An example mapping follows.<br> * and populating the placeholders with the data retrieved from LDAP. This system should
* </p> * be flexible enough to handle any client's vCard format. An example mapping follows.<br>
* <tt>ldap.vcard-mapping = * </p>
* &lt;![CDATA[ * <tt>ldap.vcard-mapping =
* &lt;vCard xmlns='vcard-temp'&gt; * &lt;![CDATA[
* &lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt; * &lt;vCard xmlns='vcard-temp'&gt;
* &lt;NICKNAME attrs=&quot;uid&quot;&gt;{0}&lt;/NICKNAME&gt; * &lt;FN attrs=&quot;displayName&quot;&gt;{0}&lt;/FN&gt;
* &lt;BDAY attrs=&quot;dob&quot;&gt;{0}&lt;/BDAY&gt; * &lt;NICKNAME attrs=&quot;uid&quot;&gt;{0}&lt;/NICKNAME&gt;
* &lt;ADR&gt; * &lt;BDAY attrs=&quot;dob&quot;&gt;{0}&lt;/BDAY&gt;
* &lt;HOME/&gt; * &lt;ADR&gt;
* &lt;EXTADR&gt;Ste 500&lt;/EXTADR&gt; * &lt;HOME/&gt;
* &lt;STREET&gt;317 SW Alder St&lt;/STREET&gt; * &lt;EXTADR&gt;Ste 500&lt;/EXTADR&gt;
* &lt;LOCALITY&gt;Portland&lt;/LOCALITY&gt; * &lt;STREET&gt;317 SW Alder St&lt;/STREET&gt;
* &lt;REGION&gt;Oregon&lt;/REGION&gt; * &lt;LOCALITY&gt;Portland&lt;/LOCALITY&gt;
* &lt;PCODE&gt;97204&lt;/PCODE&gt; * &lt;REGION&gt;Oregon&lt;/REGION&gt;
* &lt;CTRY&gt;USA&lt;/CTRY&gt; * &lt;PCODE&gt;97204&lt;/PCODE&gt;
* &lt;/ADR&gt; * &lt;CTRY&gt;USA&lt;/CTRY&gt;
* &lt;TEL&gt; * &lt;/ADR&gt;
* &lt;HOME/&gt; * &lt;TEL&gt;
* &lt;VOICE/&gt; * &lt;HOME/&gt;
* &lt;NUMBER attrs=&quot;telephoneNumber&quot;&gt;{0}&lt;/NUMBER&gt; * &lt;VOICE/&gt;
* &lt;/TEL&gt; * &lt;NUMBER attrs=&quot;telephoneNumber&quot;&gt;{0}&lt;/NUMBER&gt;
* &lt;EMAIL&gt; * &lt;/TEL&gt;
* &lt;INTERNET/&gt; * &lt;EMAIL&gt;
* &lt;USERID attrs=&quot;mail&quot;&gt;{0}&lt;/USERID&gt; * &lt;INTERNET/&gt;
* &lt;/EMAIL&gt; * &lt;USERID attrs=&quot;mail&quot;&gt;{0}&lt;/USERID&gt;
* &lt;TITLE attrs=&quot;title&quot;&gt;{0}&lt;/TITLE&gt; * &lt;/EMAIL&gt;
* &lt;ROLE attrs=&quot;&quot;&gt;{0}&lt;/ROLE&gt; * &lt;TITLE attrs=&quot;title&quot;&gt;{0}&lt;/TITLE&gt;
* &lt;ORG&gt; * &lt;ROLE attrs=&quot;&quot;&gt;{0}&lt;/ROLE&gt;
* &lt;ORGNAME attrs=&quot;o&quot;&gt;{0}&lt;/ORGNAME&gt; * &lt;ORG&gt;
* &lt;ORGUNIT attrs=&quot;&quot;&gt;{0}&lt;/ORGUNIT&gt; * &lt;ORGNAME attrs=&quot;o&quot;&gt;{0}&lt;/ORGNAME&gt;
* &lt;/ORG&gt; * &lt;ORGUNIT attrs=&quot;&quot;&gt;{0}&lt;/ORGUNIT&gt;
* &lt;URL attrs=&quot;labeledURI&quot;&gt;{0}&lt;/URL&gt; * &lt;/ORG&gt;
* &lt;DESC attrs=&quot;uidNumber,homeDirectory,loginShell&quot;&gt; * &lt;URL attrs=&quot;labeledURI&quot;&gt;{0}&lt;/URL&gt;
* uid: {0} home: {1} shell: {2} * &lt;DESC attrs=&quot;uidNumber,homeDirectory,loginShell&quot;&gt;
* &lt;/DESC&gt; * uid: {0} home: {1} shell: {2}
* &lt;/vCard&gt; * &lt;/DESC&gt;
* ]]&gt; * &lt;/vCard&gt;
* </tt> * ]]&gt;
* <p> * </tt>
* An easy way to get the vcard format your client needs, assuming you've been * <p>
* using the database store, is to do a <code>SELECT value FROM ofVCard WHERE * An easy way to get the vcard format your client needs, assuming you've been
* username='some_user'</code> in your favorite sql querier and paste the result * using the database store, is to do a <code>SELECT value FROM ofVCard WHERE
* into the <code>vcard-mapping</code> (don't forget the CDATA).</p> * 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 *
*/ * @author rkelly
public class LdapVCardProvider implements VCardProvider, PropertyEventListener { */
public class LdapVCardProvider implements VCardProvider, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(LdapVCardProvider.class);
private static final Logger Log = LoggerFactory.getLogger(LdapVCardProvider.class);
private LdapManager manager;
private VCardTemplate template; private LdapManager manager;
private Boolean dbStorageEnabled = false; 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. * 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. *
*/ * This is used/created only if we are storing avatars in the database.
private DefaultVCardProvider defaultProvider = null; */
private DefaultVCardProvider defaultProvider = null;
public LdapVCardProvider() {
// Convert XML based provider setup to Database based public LdapVCardProvider() {
JiveGlobals.migrateProperty("ldap.vcard-mapping"); // Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("ldap.vcard-mapping");
manager = LdapManager.getInstance();
initTemplate(); manager = LdapManager.getInstance();
// Listen to property events so that the template is always up to date initTemplate();
PropertyEventDispatcher.addListener(this); // Listen to property events so that the template is always up to date
// DB vcard provider used for loading properties overwritten in the DB PropertyEventDispatcher.addListener(this);
defaultProvider = new DefaultVCardProvider(); // DB vcard provider used for loading properties overwritten in the DB
// Check of avatars can be overwritten (and stored in the database) defaultProvider = new DefaultVCardProvider();
dbStorageEnabled = JiveGlobals.getBooleanProperty("ldap.override.avatar", false); // 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. /**
*/ * Initializes the VCard template as set by the administrator.
private void initTemplate() { */
String property = JiveGlobals.getProperty("ldap.vcard-mapping"); private void initTemplate() {
Log.debug("LdapVCardProvider: Found vcard mapping: '" + property); String property = JiveGlobals.getProperty("ldap.vcard-mapping");
try { Log.debug("LdapVCardProvider: Found vcard mapping: '" + property);
// Remove CDATA wrapping element try {
if (property.startsWith("<![CDATA[")) { // Remove CDATA wrapping element
property = property.substring(9, property.length()-3); if (property.startsWith("<![CDATA[")) {
} property = property.substring(9, property.length()-3);
Document document = DocumentHelper.parseText(property); }
template = new VCardTemplate(document); Document document = DocumentHelper.parseText(property);
} template = new VCardTemplate(document);
catch (Exception e) { }
Log.error("Error loading vcard mapping: " + e.getMessage()); catch (Exception e) {
} Log.error("Error loading vcard mapping: " + e.getMessage());
}
Log.debug("LdapVCardProvider: attributes size==" + template.getAttributes().length);
} Log.debug("LdapVCardProvider: attributes size==" + template.getAttributes().length);
}
/**
* Creates a mapping of requested LDAP attributes to their values for the given user. /**
* * 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. * @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 private Map<String, String> getLdapAttributes(String username) {
username = JID.unescapeNode(username); // Un-escape username
Map<String, String> map = new HashMap<>(); username = JID.unescapeNode(username);
Map<String, String> map = new HashMap<>();
DirContext ctx = null;
try { DirContext ctx = null;
String userDN = manager.findUserDN(username); try {
String userDN = manager.findUserDN(username);
ctx = manager.getContext(manager.getUsersBaseDN(username));
Attributes attrs = ctx.getAttributes(userDN, template.getAttributes()); 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); for (String attribute : template.getAttributes()) {
String value; javax.naming.directory.Attribute attr = attrs.get(attribute);
if (attr == null) { String value;
Log.debug("LdapVCardProvider: No ldap value found for attribute '" + attribute + "'"); if (attr == null) {
value = ""; Log.debug("LdapVCardProvider: No ldap value found for attribute '" + attribute + "'");
} value = "";
else { }
Object ob = attrs.get(attribute).get(); else {
Log.debug("LdapVCardProvider: Found attribute "+attribute+" of type: "+ob.getClass()); Object ob = attrs.get(attribute).get();
if(ob instanceof String) { Log.debug("LdapVCardProvider: Found attribute "+attribute+" of type: "+ob.getClass());
value = (String)ob; if(ob instanceof String) {
} else { value = (String)ob;
value = Base64.encodeBytes((byte[])ob); } else {
} value = Base64.encodeBytes((byte[])ob);
} }
Log.debug("LdapVCardProvider: Ldap attribute '" + attribute + "'=>'" + value + "'"); }
map.put(attribute, value); Log.debug("LdapVCardProvider: Ldap attribute '" + attribute + "'=>'" + value + "'");
} map.put(attribute, value);
return map; }
} return map;
catch (Exception e) { }
Log.error(e.getMessage(), e); catch (Exception e) {
return Collections.emptyMap(); Log.error(e.getMessage(), e);
} return Collections.emptyMap();
finally { }
try { finally {
if (ctx != null) { try {
ctx.close(); if (ctx != null) {
} ctx.close();
} }
catch (Exception e) { }
// Ignore. catch (Exception e) {
} // Ignore.
} }
} }
}
/**
* Loads the avatar from LDAP, based off the vcard template. /**
* * 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. *
* * 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. * @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) { @Override
// Un-escape username. public Element loadVCard(String username) {
username = JID.unescapeNode(username); // Un-escape username.
Map<String, String> map = getLdapAttributes(username); username = JID.unescapeNode(username);
Log.debug("LdapVCardProvider: Getting mapped vcard for " + username); Map<String, String> map = getLdapAttributes(username);
Element vcard = new VCard(template).getVCard(map); Log.debug("LdapVCardProvider: Getting mapped vcard for " + username);
// If we have a vcard from ldap, but it doesn't have an avatar filled in, then we Element vcard = new VCard(template).getVCard(map);
// may fill it with a locally stored vcard element. // If we have a vcard from ldap, but it doesn't have an avatar filled in, then we
if (dbStorageEnabled && vcard != null && (vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*"))) { // may fill it with a locally stored vcard element.
Element avatarElement = loadAvatarFromDatabase(username); if (dbStorageEnabled && vcard != null && (vcard.element("PHOTO") == null || vcard.element("PHOTO").element("BINVAL") == null || vcard.element("PHOTO").element("BINVAL").getText().matches("\\s*"))) {
if (avatarElement != null) { Element avatarElement = loadAvatarFromDatabase(username);
Log.debug("LdapVCardProvider: Adding avatar element from local storage"); if (avatarElement != null) {
Element currentElement = vcard.element("PHOTO"); Log.debug("LdapVCardProvider: Adding avatar element from local storage");
if (currentElement != null) { Element currentElement = vcard.element("PHOTO");
vcard.remove(currentElement); if (currentElement != null) {
} vcard.remove(currentElement);
vcard.add(avatarElement); }
} vcard.add(avatarElement);
} }
Log.debug("LdapVCardProvider: Returning vcard"); }
return vcard;
} if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_LOAD, PhotoResizer.PROPERTY_RESIZE_ON_LOAD_DEFAULT ) )
{
/** PhotoResizer.resizeAvatar( vcard );
* Returns a merged LDAP vCard combined with a PHOTO element provided in specified vCard. }
*
* @param username User whose vCard this is. Log.debug("LdapVCardProvider: Returning vcard");
* @param mergeVCard vCard element that we are merging PHOTO element from into the LDAP vCard. return vcard;
* @return vCard element after merging in PHOTO element to LDAP data. }
*/
private Element getMergedVCard(String username, Element mergeVCard) { /**
// Un-escape username. * Returns a merged LDAP vCard combined with a PHOTO element provided in specified vCard.
username = JID.unescapeNode(username); *
Map<String, String> map = getLdapAttributes(username); * @param username User whose vCard this is.
Log.debug("LdapVCardProvider: Retrieving LDAP mapped vcard for " + username); * @param mergeVCard vCard element that we are merging PHOTO element from into the LDAP vCard.
if (map.isEmpty()) { * @return vCard element after merging in PHOTO element to LDAP data.
return null; */
} private Element getMergedVCard(String username, Element mergeVCard) {
Element vcard = new VCard(template).getVCard(map); // Un-escape username.
if (mergeVCard == null) { username = JID.unescapeNode(username);
// No vcard passed in? Hrm. Fine, return LDAP vcard. Map<String, String> map = getLdapAttributes(username);
return vcard; Log.debug("LdapVCardProvider: Retrieving LDAP mapped vcard for " + username);
} if (map.isEmpty()) {
if (mergeVCard.element("PHOTO") == null) { return null;
// Merged vcard has no photo element, return LDAP vcard as is. }
return vcard; Element vcard = new VCard(template).getVCard(map);
} if (mergeVCard == null) {
Element photoElement = mergeVCard.element("PHOTO").createCopy(); // No vcard passed in? Hrm. Fine, return LDAP vcard.
if (photoElement == null || photoElement.element("BINVAL") == null || photoElement.element("BINVAL").getText().matches("\\s*")) { return vcard;
// We were passed something null or empty, so lets just return the LDAP based vcard. }
return vcard; if (mergeVCard.element("PHOTO") == null) {
} // Merged vcard has no photo element, return LDAP vcard as is.
// Now we need to check that the LDAP vcard doesn't have a PHOTO element that's filled in. return vcard;
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; Element photoElement = mergeVCard.element("PHOTO").createCopy();
return vcard; 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.
Log.debug("LdapVCardProvider: Merging avatar element from passed vcard"); return vcard;
Element currentElement = vcard.element("PHOTO"); }
if (currentElement != null) { // Now we need to check that the LDAP vcard doesn't have a PHOTO element that's filled in.
vcard.remove(currentElement); 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;
vcard.add(photoElement); return vcard;
return vcard; }
} Log.debug("LdapVCardProvider: Merging avatar element from passed vcard");
Element currentElement = vcard.element("PHOTO");
/** if (currentElement != null) {
* Loads the avatar element from the user's DB stored vcard. vcard.remove(currentElement);
* }
* @param username User whose vcard/avatar element we are loading. vcard.add(photoElement);
* @return Loaded avatar element or null if not found. return vcard;
*/ }
private Element loadAvatarFromDatabase(String username) {
Element vcardElement = defaultProvider.loadVCard(username); /**
Element avatarElement = null; * Loads the avatar element from the user's DB stored vcard.
if (vcardElement != null && vcardElement.element("PHOTO") != null) { *
avatarElement = vcardElement.element("PHOTO").createCopy(); * @param username User whose vcard/avatar element we are loading.
} * @return Loaded avatar element or null if not found.
return avatarElement; */
} private Element loadAvatarFromDatabase(String username) {
Element vcardElement = defaultProvider.loadVCard(username);
/** Element avatarElement = null;
* Handles when a user creates a new vcard. if (vcardElement != null && vcardElement.element("PHOTO") != null) {
* avatarElement = vcardElement.element("PHOTO").createCopy();
* @param username User that created a new vcard. }
* @param vCardElement vCard element containing the new vcard. return avatarElement;
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode. }
*/
@Override /**
public Element createVCard(String username, Element vCardElement) * Handles when a user creates a new vcard.
throws UnsupportedOperationException, AlreadyExistsException { *
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed."); * @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.
/** */
* Handles when a user updates their vcard. @Override
* public Element createVCard(String username, Element vCardElement)
* @param username User that updated their vcard. throws UnsupportedOperationException, AlreadyExistsException {
* @param vCardElement vCard element containing the new vcard. throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
* @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode. }
*/
@Override /**
public Element updateVCard(String username, Element vCardElement) throws UnsupportedOperationException { * Handles when a user updates their vcard.
if (dbStorageEnabled && defaultProvider != null) { *
if (isValidVCardChange(username, vCardElement)) { * @param username User that updated their vcard.
Element mergedVCard = getMergedVCard(username, vCardElement); * @param vCardElement vCard element containing the new vcard.
try { * @throws UnsupportedOperationException If an invalid field is changed or we are in readonly mode.
defaultProvider.updateVCard(username, mergedVCard); */
} catch (NotFoundException e) { @Override
try { public Element updateVCard(String username, Element vCardElement) throws UnsupportedOperationException {
defaultProvider.createVCard(username, mergedVCard); if (dbStorageEnabled && defaultProvider != null) {
} catch (AlreadyExistsException e1) { if (isValidVCardChange(username, vCardElement)) {
// Ignore Element mergedVCard = getMergedVCard(username, vCardElement);
} try {
} defaultProvider.updateVCard(username, mergedVCard);
return mergedVCard; } catch (NotFoundException e) {
} try {
else { defaultProvider.createVCard(username, mergedVCard);
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes."); } catch (AlreadyExistsException e1) {
} // Ignore
} }
else { }
throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed."); return mergedVCard;
} }
} else {
throw new UnsupportedOperationException("LdapVCardProvider: Invalid vcard changes.");
/** }
* Handles when a user deletes their vcard. }
* else {
* @param username User that deketed their vcard. throw new UnsupportedOperationException("LdapVCardProvider: VCard changes not allowed.");
* @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."); * 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.
* Returns true or false if the change to the existing vcard is valid (only to PHOTO element) */
* @Override
* @param username User who's LDAP-based vcard we will compare with. public void deleteVCard(String username) throws UnsupportedOperationException {
* @param newvCard New vCard Element we will compare against. throw new UnsupportedOperationException("LdapVCardProvider: Attempted to delete vcard in read-only mode.");
* @return True or false if the changes made were valid (only to PHOTO element) }
*/
private Boolean isValidVCardChange(String username, Element newvCard) { /**
if (newvCard == null) { * Returns true or false if the change to the existing vcard is valid (only to PHOTO element)
// Well if there's nothing to change, of course it's valid. *
Log.debug("LdapVCardProvider: No new vcard provided (no changes), accepting."); * @param username User who's LDAP-based vcard we will compare with.
return true; * @param newvCard New vCard Element we will compare against.
} * @return True or false if the changes made were valid (only to PHOTO element)
// Un-escape username. */
username = JID.unescapeNode(username); private Boolean isValidVCardChange(String username, Element newvCard) {
Map<String, String> map = getLdapAttributes(username); if (newvCard == null) {
// Retrieve LDAP created vcard for comparison // Well if there's nothing to change, of course it's valid.
Element ldapvCard = new VCard(template).getVCard(map); Log.debug("LdapVCardProvider: No new vcard provided (no changes), accepting.");
if (ldapvCard == null) { return true;
// This person has no vcard at all, may not change it! }
Log.debug("LdapVCardProvider: User has no LDAP vcard, nothing they can change, rejecting."); // Un-escape username.
return false; username = JID.unescapeNode(username);
} Map<String, String> map = getLdapAttributes(username);
// If the LDAP vcard has a non-empty PHOTO element set, then there is literally no way this will be accepted. // Retrieve LDAP created vcard for comparison
Element ldapPhotoElem = ldapvCard.element("PHOTO"); Element ldapvCard = new VCard(template).getVCard(map);
if (ldapPhotoElem != null) { if (ldapvCard == null) {
Element ldapBinvalElem = ldapPhotoElem.element("BINVAL"); // This person has no vcard at all, may not change it!
if (ldapBinvalElem != null && !ldapBinvalElem.getTextTrim().matches("\\s*")) { Log.debug("LdapVCardProvider: User has no LDAP vcard, nothing they can change, rejecting.");
// LDAP is providing a valid PHOTO element, byebye! return false;
Log.debug("LdapVCardProvider: LDAP has a PHOTO element set, no way to override, 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) {
// Retrieve database vcard, if it exists Element ldapBinvalElem = ldapPhotoElem.element("BINVAL");
Element dbvCard = defaultProvider.loadVCard(username); if (ldapBinvalElem != null && !ldapBinvalElem.getTextTrim().matches("\\s*")) {
if (dbvCard != null) { // LDAP is providing a valid PHOTO element, byebye!
Element dbPhotoElem = dbvCard.element("PHOTO"); Log.debug("LdapVCardProvider: LDAP has a PHOTO element set, no way to override, rejecting.");
if (dbPhotoElem == null) { return false;
// DB has no photo, lets accept what we got. }
Log.debug("LdapVCardProvider: Database has no PHOTO element, accepting update."); }
return true; // Retrieve database vcard, if it exists
} Element dbvCard = defaultProvider.loadVCard(username);
else { if (dbvCard != null) {
Element newPhotoElem = newvCard.element("PHOTO"); Element dbPhotoElem = dbvCard.element("PHOTO");
if (newPhotoElem == null) { if (dbPhotoElem == null) {
Log.debug("LdapVCardProvider: Photo element was removed, accepting update."); // DB has no photo, lets accept what we got.
return true; Log.debug("LdapVCardProvider: Database has no PHOTO element, accepting update.");
} return true;
// Note: NodeComparator never seems to consider these equal, even if they are? }
if (!dbPhotoElem.asXML().equals(newPhotoElem.asXML())) { else {
// Photo element was changed. Ignore all other changes and accept this. Element newPhotoElem = newvCard.element("PHOTO");
Log.debug("LdapVCardProvider: PHOTO element changed, accepting update."); if (newPhotoElem == null) {
return true; Log.debug("LdapVCardProvider: Photo element was removed, accepting update.");
} return true;
} }
} // Note: NodeComparator never seems to consider these equal, even if they are?
else { if (!dbPhotoElem.asXML().equals(newPhotoElem.asXML())) {
// No vcard exists in database // Photo element was changed. Ignore all other changes and accept this.
Log.debug("LdapVCardProvider: Database has no vCard stored, accepting update."); Log.debug("LdapVCardProvider: PHOTO element changed, accepting update.");
return true; 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 else {
// So we'll consider this a bad return. // No vcard exists in database
Log.debug("LdapVCardProvider: PHOTO element didn't change, no reason to accept this, rejecting."); Log.debug("LdapVCardProvider: Database has no vCard stored, accepting update.");
return false; return true;
} }
// Ok, either something bad changed or nothing changed. Either way, user either:
// 1. should not have tried to change something 'readonly'
@Override // 2. shouldn't have bothered submitting no changes
public boolean isReadOnly() { // So we'll consider this a bad return.
return !dbStorageEnabled; Log.debug("LdapVCardProvider: PHOTO element didn't change, no reason to accept this, rejecting.");
} return false;
}
@Override
public void propertySet(String property, Map params) {
if ("ldap.override.avatar".equals(property)) { @Override
dbStorageEnabled = Boolean.parseBoolean((String)params.get("value")); public boolean isReadOnly() {
} return !dbStorageEnabled;
else if ("ldap.vcard-mapping".equals(property)) { }
initTemplate();
// Reset cache of vCards @Override
VCardManager.getInstance().reset(); public void propertySet(String property, Map params) {
} if ("ldap.override.avatar".equals(property)) {
} dbStorageEnabled = Boolean.parseBoolean((String)params.get("value"));
}
@Override else if ("ldap.vcard-mapping".equals(property)) {
public void propertyDeleted(String property, Map params) { initTemplate();
if ("ldap.override.avatar".equals(property)) { // Reset cache of vCards
dbStorageEnabled = false; VCardManager.getInstance().reset();
} }
} }
@Override @Override
public void xmlPropertySet(String property, Map params) { public void propertyDeleted(String property, Map params) {
//Ignore if ("ldap.override.avatar".equals(property)) {
} dbStorageEnabled = false;
}
@Override }
public void xmlPropertyDeleted(String property, Map params) {
//Ignore @Override
} public void xmlPropertySet(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 @Override
* a <code>Map</code> of ldap attributes to ldap values via public void xmlPropertyDeleted(String property, Map params) {
* <code>MessageFormat</code> //Ignore
* }
* @author rkelly
*/ /**
private static class VCardTemplate { * Class to hold a <code>Document</code> representation of a vcard mapping
* and unique attribute placeholders. Used by <code>VCard</code> to apply
private Document document; * a <code>Map</code> of ldap attributes to ldap values via
* <code>MessageFormat</code>
private String[] attributes; *
* @author rkelly
public VCardTemplate(Document document) { */
Set<String> set = new HashSet<>(); private static class VCardTemplate {
this.document = document;
treeWalk(this.document.getRootElement(), set); private Document document;
attributes = set.toArray(new String[set.size()]);
} private String[] attributes;
public String[] getAttributes() { public VCardTemplate(Document document) {
return attributes; Set<String> set = new HashSet<>();
} this.document = document;
treeWalk(this.document.getRootElement(), set);
public Document getDocument() { attributes = set.toArray(new String[set.size()]);
return document; }
}
public String[] getAttributes() {
private void treeWalk(Element element, Set<String> set) { return attributes;
for (int i = 0, size = element.nodeCount(); i < size; i++) { }
Node node = element.node(i);
if (node instanceof Element) { public Document getDocument() {
Element emement = (Element) node; return document;
}
StringTokenizer st = new StringTokenizer(emement.getTextTrim(), ", //{}");
while (st.hasMoreTokens()) { private void treeWalk(Element element, Set<String> set) {
// Remove enclosing {} for (int i = 0, size = element.nodeCount(); i < size; i++) {
String string = st.nextToken().replaceAll("(\\{)([\\d\\D&&[^}]]+)(})", "$2"); Node node = element.node(i);
Log.debug("VCardTemplate: found attribute " + string); if (node instanceof Element) {
set.add(string); Element emement = (Element) node;
}
treeWalk(emement, set); 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);
/** }
* vCard class that converts vcard data using a template. treeWalk(emement, set);
*/ }
private static class VCard { }
}
private VCardTemplate template; }
public VCard(VCardTemplate template) { /**
this.template = template; * vCard class that converts vcard data using a template.
} */
private static class VCard {
public Element getVCard(Map<String, String> map) {
Document document = (Document) template.getDocument().clone(); private VCardTemplate template;
Element element = document.getRootElement();
return treeWalk(element, map); public VCard(VCardTemplate template) {
} this.template = template;
}
private Element treeWalk(Element element, Map<String, String> map) {
for (int i = 0, size = element.nodeCount(); i < size; i++) { public Element getVCard(Map<String, String> map) {
Node node = element.node(i); Document document = (Document) template.getDocument().clone();
if (node instanceof Element) { Element element = document.getRootElement();
Element emement = (Element) node; return treeWalk(element, map);
}
String elementText = emement.getTextTrim();
if (elementText != null && !"".equals(elementText)) { private Element treeWalk(Element element, Map<String, String> map) {
String format = emement.getStringValue(); for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
StringTokenizer st = new StringTokenizer(elementText, ", //{}"); if (node instanceof Element) {
while (st.hasMoreTokens()) { Element emement = (Element) node;
// Remove enclosing {}
String field = st.nextToken(); String elementText = emement.getTextTrim();
String attrib = field.replaceAll("(\\{)(" + field + ")(})", "$2"); if (elementText != null && !"".equals(elementText)) {
String value = map.get(attrib); String format = emement.getStringValue();
format = format.replaceFirst("(\\{)(" + field + ")(})", Matcher.quoteReplacement(value));
} StringTokenizer st = new StringTokenizer(elementText, ", //{}");
emement.setText(format); while (st.hasMoreTokens()) {
} // Remove enclosing {}
treeWalk(emement, map); String field = st.nextToken();
} String attrib = field.replaceAll("(\\{)(" + field + ")(})", "$2");
} String value = map.get(attrib);
return element; format = format.replaceFirst("(\\{)(" + field + ")(})", Matcher.quoteReplacement(value));
} }
} emement.setText(format);
} }
treeWalk(emement, map);
}
}
return element;
}
}
}
...@@ -28,6 +28,7 @@ import org.dom4j.Element; ...@@ -28,6 +28,7 @@ import org.dom4j.Element;
import org.dom4j.io.SAXReader; import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.AlreadyExistsException; import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException; import org.jivesoftware.util.NotFoundException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -98,6 +99,12 @@ public class DefaultVCardProvider implements VCardProvider { ...@@ -98,6 +99,12 @@ public class DefaultVCardProvider implements VCardProvider {
} }
DbConnectionManager.closeConnection(rs, pstmt, con); DbConnectionManager.closeConnection(rs, pstmt, con);
} }
if ( JiveGlobals.getBooleanProperty( PhotoResizer.PROPERTY_RESIZE_ON_LOAD, PhotoResizer.PROPERTY_RESIZE_ON_LOAD_DEFAULT ) )
{
PhotoResizer.resizeAvatar( vCardElement );
}
return vCardElement; return vCardElement;
} }
} }
...@@ -109,6 +116,11 @@ public class DefaultVCardProvider implements VCardProvider { ...@@ -109,6 +116,11 @@ public class DefaultVCardProvider implements VCardProvider {
throw new AlreadyExistsException("Username " + username + " already has a vCard"); 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; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
...@@ -133,6 +145,12 @@ public class DefaultVCardProvider implements VCardProvider { ...@@ -133,6 +145,12 @@ public class DefaultVCardProvider implements VCardProvider {
// The user already has a vCard // The user already has a vCard
throw new NotFoundException("Username " + username + " does not have 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; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
......
package org.igniterealtime.openfire.plugin.avatarresizer; package org.jivesoftware.openfire.vcard;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.util.Base64; import org.jivesoftware.util.Base64;
...@@ -21,9 +21,22 @@ import java.util.Iterator; ...@@ -21,9 +21,22 @@ import java.util.Iterator;
/** /**
* Image resizing utility methods. * 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 ) public static void resizeAvatar( final Element vCardElement )
{ {
...@@ -63,7 +76,7 @@ public class Resizer ...@@ -63,7 +76,7 @@ public class Resizer
final byte[] original = Base64.decode( element.getTextTrim() ); final byte[] original = Base64.decode( element.getTextTrim() );
// Crop and shrink, if needed. // 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 ); final byte[] resized = cropAndShrink( original, targetDimension, iw );
// If a resized image was created, replace to original avatar in the VCard. // 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 @@ ...@@ -9,7 +9,6 @@
<modules> <modules>
<module>openfire-plugin-assembly-descriptor</module> <module>openfire-plugin-assembly-descriptor</module>
<module>avatarResizer</module>
<module>bookmarks</module> <module>bookmarks</module>
<module>broadcast</module> <module>broadcast</module>
<module>callbackOnOffline</module> <module>callbackOnOffline</module>
...@@ -199,4 +198,4 @@ ...@@ -199,4 +198,4 @@
</snapshots> </snapshots>
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
</project> </project>
\ No newline at end of file
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