diff --git a/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/LocalizationFile.java b/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/LocalizationFile.java index 811ccdc6f1..08b78508a8 100644 --- a/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/LocalizationFile.java +++ b/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/LocalizationFile.java @@ -29,6 +29,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.xml.bind.JAXBException; + import com.raytheon.uf.common.localization.FileLocker.Type; import com.raytheon.uf.common.localization.ILocalizationAdapter.ListResponse; import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel; @@ -80,6 +82,7 @@ import com.raytheon.uf.common.status.UFStatus.Priority; * This was added as part of an effort to improve * localization performance but caused updated * files on the server not to be retrieved. + * Jan 17, 2013 1412 djohnson Add jaxbMarshal. * * * @author njensen @@ -635,6 +638,25 @@ public final class LocalizationFile implements Comparable { return null; } + /** + * Marshal the specified object into this file. + * + * @param obj + * the object to marshal + * + * @param jaxbManager + * the jaxbManager + */ + public void jaxbMarshal(Object obj, JAXBManager jaxbManager) throws LocalizationException{ + try { + String xml = jaxbManager.marshalToXml(obj); + write(xml.getBytes()); + } catch (JAXBException e) { + throw new LocalizationException( + "Unable to marshal the object to the file.", e); + } + } + @Override public String toString() { return context + IPathManager.SEPARATOR + path; diff --git a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/META-INF/MANIFEST.MF b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/META-INF/MANIFEST.MF index 76e9413bb9..57095e23d0 100644 --- a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/META-INF/MANIFEST.MF +++ b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/META-INF/MANIFEST.MF @@ -10,7 +10,8 @@ Require-Bundle: com.raytheon.uf.common.serialization;bundle-version="1.12.2", com.raytheon.uf.common.auth;bundle-version="1.12.1174", com.raytheon.uf.common.status;bundle-version="1.12.1174", com.raytheon.uf.common.localization;bundle-version="1.12.1174", - com.raytheon.uf.common.serialization.comm;bundle-version="1.12.1174" + com.raytheon.uf.common.serialization.comm;bundle-version="1.12.1174", + org.apache.commons.lang;bundle-version="2.3.0" Export-Package: com.raytheon.uf.common.plugin.nwsauth, com.raytheon.uf.common.plugin.nwsauth.exception, com.raytheon.uf.common.plugin.nwsauth.user, diff --git a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/NwsRoleData.java b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/NwsRoleData.java index 6dd1df05b6..8194b50017 100644 --- a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/NwsRoleData.java +++ b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/NwsRoleData.java @@ -302,4 +302,21 @@ public class NwsRoleData implements ISerializableObject { return false; } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Application:").append(this.getApplication()).append("\n\n"); + sb.append("Users:\n").append(this.getUserList()).append("\n\n"); + sb.append("Permissions:\n").append(this.getPermissionList()) + .append("\n\n"); + sb.append("Roles:\n").append(this.getRoleList()).append("\n\n"); + + return sb.toString(); + } } diff --git a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/PermissionXML.java b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/PermissionXML.java index 824f9af8de..1966f492ea 100644 --- a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/PermissionXML.java +++ b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/PermissionXML.java @@ -94,4 +94,16 @@ public class PermissionXML implements ISerializableObject { public void setDescription(String description) { this.description = (description == null) ? null : description.trim(); } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("id:").append(this.getId()); + sb.append("\ndescription:").append(this.getDescription()); + + return sb.toString(); + } } diff --git a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/RoleXML.java b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/RoleXML.java index d17db43ea3..5802aa8118 100644 --- a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/RoleXML.java +++ b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/RoleXML.java @@ -117,4 +117,19 @@ public class RoleXML implements ISerializableObject { public void addPermission(String permission) { this.permissionList.add(permission); } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("roleId:").append(this.getRoleId()); + sb.append("\nroleDescription:").append(this.getRoleDescription()); + sb.append("\npermissionList:").append(this.getPermissionList()); + + return sb.toString(); + } } diff --git a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/UserXML.java b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/UserXML.java index aa8089901e..61f9a5d19e 100644 --- a/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/UserXML.java +++ b/edexOsgi/com.raytheon.uf.common.plugin.nwsauth/src/com/raytheon/uf/common/plugin/nwsauth/xml/UserXML.java @@ -28,6 +28,9 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + import com.raytheon.uf.common.auth.user.IAuthenticationData; import com.raytheon.uf.common.auth.user.IUser; import com.raytheon.uf.common.auth.user.IUserId; @@ -66,6 +69,17 @@ public class UserXML implements IUser, ISerializableObject { @XmlElements({ @XmlElement(name = "userRole", type = String.class) }) private List roleList = new ArrayList(); + public UserXML() { + + } + + /** + * @param userId + */ + public UserXML(String userId) { + setUserId(userId); + } + /** * @return the userId */ @@ -129,6 +143,30 @@ public class UserXML implements IUser, ISerializableObject { } } + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof UserXML) { + UserXML that = (UserXML) obj; + + EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.getUserId(), that.getUserId()); + return builder.isEquals(); + + } + return super.equals(obj); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return new HashCodeBuilder().append(this.getUserId()).toHashCode(); + } + /* * (non-Javadoc) * @@ -136,17 +174,10 @@ public class UserXML implements IUser, ISerializableObject { */ @Override public String toString() { - final String nl = "\n"; StringBuilder sb = new StringBuilder(); - sb.append(this.getUserId()).append(nl); - - for (String role : this.roleList) { - sb.append(" ").append(role).append(nl); - } - - for (String perm : permissionList) { - sb.append(" ").append(perm).append(nl); - } + sb.append("userId:").append(this.getUserId()); + sb.append("\nroles:").append(this.getRoleList()); + sb.append("\npermissions:").append(this.getPermissionList()); return sb.toString(); } diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/META-INF/MANIFEST.MF b/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/META-INF/MANIFEST.MF index 32525a99e7..4b8090eb4f 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/META-INF/MANIFEST.MF +++ b/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/META-INF/MANIFEST.MF @@ -11,7 +11,8 @@ Require-Bundle: com.raytheon.uf.edex.auth;bundle-version="1.12.2", com.raytheon.uf.common.status;bundle-version="1.12.1174", com.raytheon.uf.common.localization, com.raytheon.uf.common.serialization.comm;bundle-version="1.12.1174", - com.raytheon.uf.common.useradmin;bundle-version="1.0.0" + com.raytheon.uf.common.useradmin;bundle-version="1.0.0", + com.raytheon.uf.common.time;bundle-version="1.12.1174" Import-Package: com.raytheon.uf.common.localization, com.raytheon.uf.common.serialization, com.raytheon.uf.common.status, diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/src/com/raytheon/uf/edex/plugin/nwsauth/FileManager.java b/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/src/com/raytheon/uf/edex/plugin/nwsauth/FileManager.java index c308db748d..13d71d3320 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/src/com/raytheon/uf/edex/plugin/nwsauth/FileManager.java +++ b/edexOsgi/com.raytheon.uf.edex.plugin.nwsauth/src/com/raytheon/uf/edex/plugin/nwsauth/FileManager.java @@ -19,9 +19,11 @@ **/ package com.raytheon.uf.edex.plugin.nwsauth; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; import javax.xml.bind.JAXBException; @@ -40,6 +42,7 @@ import com.raytheon.uf.common.serialization.JAXBManager; import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.common.status.UFStatus.Priority; +import com.raytheon.uf.common.time.util.TimeUtil; /** * Uses localization data to determine role/permissions. Intentionally @@ -51,7 +54,9 @@ import com.raytheon.uf.common.status.UFStatus.Priority; * * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- - * Jan 09, 2013 1412 djohnson Moved file writing from viz plugin to server-side. + * Jan 09, 2013 1412 djohnson Moved file writing from viz plugin to server-side. + * Jan 17, 2013 1412 djohnson Check files for having been modified each time data is requested, + * in case they were written by another member of the cluster. * * * @@ -69,14 +74,22 @@ class FileManager { private final String ROLE_DIR = "roles"; - private final Map roleDataMap = new HashMap(); + private final AtomicLong lastReadTime = new AtomicLong(-1L); + + /** + * Application name -> Role Data. + */ + private final ConcurrentMap roleDataMap = new ConcurrentHashMap(); /** * Application name -> LocalizationFile map. */ - private final Map roleFileMap = new HashMap(); + private final ConcurrentMap roleFileMap = new ConcurrentHashMap(); - private FileManager() { + /** + * Package-level visibility so tests can create new instances. + */ + FileManager() { readXML(); } @@ -117,31 +130,54 @@ class FileManager { private void readXML() { try { - getJaxbManager(); - - IPathManager pm = PathManagerFactory.getPathManager(); - LocalizationContext[] contexts = new LocalizationContext[2]; - contexts[0] = pm.getContext(LocalizationType.COMMON_STATIC, - LocalizationLevel.BASE); - contexts[1] = pm.getContext(LocalizationType.COMMON_STATIC, - LocalizationLevel.SITE); - LocalizationFile[] roleFiles = pm.listFiles(contexts, ROLE_DIR, - new String[] { ".xml" }, false, true); - + LocalizationFile[] roleFiles = getUserRoleLocalizationFiles(); + boolean needToReadFiles = false; for (LocalizationFile lf : roleFiles) { - NwsRoleData roleData = lf.jaxbUnmarshal(NwsRoleData.class, - getJaxbManager()); + final long fileLastModified = lf.getFile().lastModified(); + final long lastTimeFilesWereRead = lastReadTime.get(); - if (roleData != null) { - this.roleDataMap.put(roleData.getApplication(), roleData); - this.roleFileMap.put(roleData.getApplication(), lf); + if (fileLastModified > lastTimeFilesWereRead) { + needToReadFiles = true; + break; } } + + if (needToReadFiles) { + for (LocalizationFile lf : roleFiles) { + final long fileLastModified = lf.getFile().lastModified(); + final long lastTimeFilesWereRead = lastReadTime.get(); + + if (fileLastModified < lastTimeFilesWereRead) { + continue; + } + NwsRoleData roleData = lf.jaxbUnmarshal(NwsRoleData.class, + getJaxbManager()); + + if (roleData != null) { + final String application = roleData.getApplication(); + this.roleDataMap.put(application, roleData); + this.roleFileMap.put(application, lf); + } + } + } + lastReadTime.set(TimeUtil.currentTimeMillis()); } catch (Exception e) { statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(), e); } } + private LocalizationFile[] getUserRoleLocalizationFiles() { + IPathManager pm = PathManagerFactory.getPathManager(); + LocalizationContext[] contexts = new LocalizationContext[2]; + contexts[0] = pm.getContext(LocalizationType.COMMON_STATIC, + LocalizationLevel.BASE); + contexts[1] = pm.getContext(LocalizationType.COMMON_STATIC, + LocalizationLevel.SITE); + LocalizationFile[] roleFiles = pm.listFiles(contexts, ROLE_DIR, + new String[] { ".xml" }, false, true); + return roleFiles; + } + private JAXBManager getJaxbManager() throws JAXBException { if (jaxbManager == null) { jaxbManager = new JAXBManager(NwsRoleData.class, @@ -154,13 +190,15 @@ class FileManager { * @return */ public Map getRoleDataMap() { + readXML(); return roleDataMap; } /** * @param roleDataWithChanges */ - public void writeApplicationRoleData(Map roleDataWithChanges) { + public void writeApplicationRoleData( + Map roleDataWithChanges) { for (Entry entry : roleDataWithChanges.entrySet()) { final String application = entry.getKey(); roleDataMap.put(application, entry.getValue()); diff --git a/tests/.classpath b/tests/.classpath index 65be975a6f..c7d4e8274f 100644 --- a/tests/.classpath +++ b/tests/.classpath @@ -67,5 +67,6 @@ + diff --git a/tests/unit/com/raytheon/uf/common/localization/TestPathManager.java b/tests/unit/com/raytheon/uf/common/localization/TestPathManager.java index 0190fd04d0..dbd105ca8e 100644 --- a/tests/unit/com/raytheon/uf/common/localization/TestPathManager.java +++ b/tests/unit/com/raytheon/uf/common/localization/TestPathManager.java @@ -165,7 +165,7 @@ public class TestPathManager extends PathManager { try { if (baselinedVersion.exists()) { if (baselinedVersion.isDirectory()) { - savedFile.mkdirs(); + FileUtil.copyDirectory(baselinedVersion, savedFile); } else { FileUtil.copyFile(baselinedVersion, savedFile); } diff --git a/tests/unit/com/raytheon/uf/edex/plugin/nwsauth/FileManagerTest.java b/tests/unit/com/raytheon/uf/edex/plugin/nwsauth/FileManagerTest.java new file mode 100644 index 0000000000..820336d9f7 --- /dev/null +++ b/tests/unit/com/raytheon/uf/edex/plugin/nwsauth/FileManagerTest.java @@ -0,0 +1,156 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.edex.plugin.nwsauth; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import javax.xml.bind.JAXBException; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.raytheon.uf.common.localization.IPathManager; +import com.raytheon.uf.common.localization.LocalizationContext; +import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel; +import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType; +import com.raytheon.uf.common.localization.LocalizationFile; +import com.raytheon.uf.common.localization.PathManagerFactory; +import com.raytheon.uf.common.localization.PathManagerFactoryTest; +import com.raytheon.uf.common.localization.exception.LocalizationException; +import com.raytheon.uf.common.plugin.nwsauth.xml.NwsRoleData; +import com.raytheon.uf.common.plugin.nwsauth.xml.UserXML; +import com.raytheon.uf.common.serialization.JAXBManager; +import com.raytheon.uf.common.time.util.TimeUtil; +import com.raytheon.uf.common.time.util.TimeUtilTest; + +/** + * Test {@link FileManager}. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jan 17, 2013 1412       djohnson     Initial creation
+ * 
+ * 
+ * + * @author djohnson + * @version 1.0 + */ + +public class FileManagerTest { + private static JAXBManager jaxbManager; + + private final UserXML someUser = new UserXML("someUser"); + + private FileManager manager; + + @BeforeClass + public static void classSetup() throws JAXBException { + jaxbManager = new JAXBManager(NwsRoleData.class); + } + + @Before + public void setUp() { + TimeUtilTest.freezeTime(); + PathManagerFactoryTest.initLocalization(); + manager = new FileManager(); + } + + @After + public void tearDown() { + TimeUtilTest.resumeTime(); + } + + @Test + public void fileNewerOnDiskIsReadBeforeResponse() + throws LocalizationException { + + addUserToUserAdminFile(); + + verifyUserIsFoundWhenRoleDataRetrieved(); + } + + @Test + public void fileOlderOnDiskIsNotReadBeforeResponse() + throws LocalizationException { + + addUserToUserAdminFile(); + setUserAdminFileModifiedTimeToOneSecondAgo(); + + verifyUserIsNotFoundWhenRoleDataRetrieved(); + } + + private void verifyUserIsFoundWhenRoleDataRetrieved() { + final Map roleDataMap = manager.getRoleDataMap(); + assertTrue( + "Did not find the user added to the role data map!", + roleDataMap.get("TestUserRoles").getUserList() + .contains(someUser)); + } + + /** + * @param someUser + */ + private void verifyUserIsNotFoundWhenRoleDataRetrieved() { + final Map roleDataMap = manager.getRoleDataMap(); + assertFalse( + "Should not have found the user added to the role data map!", + roleDataMap.get("TestUserRoles").getUserList() + .contains(someUser)); + + } + + private void addUserToUserAdminFile() throws LocalizationException { + final LocalizationFile file = getTestUserAdminRolesLocalizationFile(); + NwsRoleData roleData = file.jaxbUnmarshal(NwsRoleData.class, + jaxbManager); + + roleData.getUserList().add(someUser); + file.jaxbMarshal(roleData, jaxbManager); + file.save(); + // The file was written out 1 second after we last read it + file.getFile().setLastModified( + TimeUtil.currentTimeMillis() + TimeUtil.MILLIS_PER_SECOND); + } + + private void setUserAdminFileModifiedTimeToOneSecondAgo() { + // The file was written out 1 second before we last read it + getTestUserAdminRolesLocalizationFile().getFile().setLastModified( + TimeUtil.currentTimeMillis() - TimeUtil.MILLIS_PER_SECOND); + } + + private LocalizationFile getTestUserAdminRolesLocalizationFile() { + IPathManager pathManager = PathManagerFactory.getPathManager(); + final LocalizationFile file = pathManager.getLocalizationFile( + new LocalizationContext(LocalizationType.COMMON_STATIC, + LocalizationLevel.SITE, "OAX"), + "roles/testUserAdminRoles.xml"); + return file; + } + +} diff --git a/tests/utility/common_static/site/OAX/roles/testUserAdminRoles.xml b/tests/utility/common_static/site/OAX/roles/testUserAdminRoles.xml new file mode 100644 index 0000000000..5e02c0ca3d --- /dev/null +++ b/tests/utility/common_static/site/OAX/roles/testUserAdminRoles.xml @@ -0,0 +1,14 @@ + + + + TestUserRoles + + + This permission allows the user to access and edit AWIPS 2 User Administration + + + + + awips.user.admin + + \ No newline at end of file