Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ public void initializeResources(CommCarePlatform platform, boolean isUpgrade) th
setMissingResources(missingResources);
}

private void attemptResourceInitialization(CommCarePlatform platform, boolean isUpgrade,
public void attemptResourceInitialization(CommCarePlatform platform, boolean isUpgrade,
Resource r, Vector<Resource> missingResources) throws ResourceInitializationException {
try {
r.getInstaller().initialize(platform, isUpgrade);
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/org/commcare/suite/model/Credential.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.commcare.suite.model;

import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.Externalizable;
import org.javarosa.core.util.externalizable.PrototypeFactory;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
* Apps use this model to convey the types of credentials it issues
*/
public class Credential implements Externalizable {

private String level;
private String type;

/**
* Serialization Only!!!
*/
public Credential() {
}

public Credential(String level, String type) {
this.level = level;
this.type = type;
}

@Override
public void readExternal(DataInputStream in, PrototypeFactory pf)
throws IOException, DeserializationException {
level = ExtUtil.readString(in);
type = ExtUtil.readString(in);
}

@Override
public void writeExternal(DataOutputStream out) throws IOException {
ExtUtil.writeString(out, level);
ExtUtil.writeString(out, type);
}

public String getLevel() {
return level;
}

public String getType() {
return type;
}

@Override
public String toString() {
return "Credential{" +
"level='" + level + '\'' +
", type='" + type + '\'' +
'}';
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/commcare/suite/model/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class Profile implements Persistable {
* were added for multiple app seating functionality
*/
private boolean fromOld;
private Vector<Credential> credentials;

@SuppressWarnings("unused")
public Profile() {
Expand Down Expand Up @@ -77,6 +78,7 @@ public Profile(int version, String authRef, String uniqueId, String displayName,
properties = new Vector<>();
roots = new Vector<>();
dependencies = new Vector<>();
credentials = new Vector<>();
featureStatus = new Hashtable<>();
//turn on default features
featureStatus.put("users", true);
Expand Down Expand Up @@ -191,6 +193,14 @@ public void setDependencies(Vector<AndroidPackageDependency> dependencies) {
this.dependencies = dependencies;
}

public void setCredentials(Vector<Credential> credentials) {
this.credentials = credentials;
}

public Vector<Credential> getCredentials() {
return credentials;
}

/**
* A helper method which initializes the properties specified
* by this profile definition.
Expand Down Expand Up @@ -228,6 +238,8 @@ public void readExternal(DataInputStream in, PrototypeFactory pf)
buildProfileId = ExtUtil.readString(in);
dependencies = (Vector<AndroidPackageDependency>)ExtUtil.read(in,
new ExtWrapList(AndroidPackageDependency.class), pf);
credentials = (Vector<Credential>)ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
}
Comment on lines +241 to 243
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Potential backward-compat break in externalization.

Older serialized Profiles (pre-credentials) would lack trailing bytes; reading will EOF. Guard read to default to empty when the stream ends.

-        credentials = (Vector<Credential>)ExtUtil.read(in,
-                new ExtWrapList(Credential.class), pf);
+        try {
+            credentials = (Vector<Credential>) ExtUtil.read(in, new ExtWrapList(Credential.class), pf);
+        } catch (java.io.EOFException eof) {
+            // Backward-compat: profiles serialized before credentials existed
+            credentials = new Vector<>();
+        }

Run to confirm Profiles aren’t persisted across versions (or identify call sites to test upgrade paths):


🏁 Script executed:

#!/bin/bash
# Locate any storage usage of Profile objects/keys
rg -nP -C2 '\bProfile\.STORAGE_KEY\b|"PROFILE"' 
rg -nP -C3 'IStorageUtility\s*<\s*Profile|StorageManager.*Profile|save\(.+Profile|write\(.+Profile'

Length of output: 2940


🏁 Script executed:

#!/bin/bash
sed -n '200,260p' src/main/java/org/commcare/suite/model/Profile.java

Length of output: 2819


Guard credentials deserialization for backward‐compatibility
In Profile.readExternal (src/main/java/org/commcare/suite/model/Profile.java), wrap the credentials read in a try/catch for EOFException so legacy profiles without credentials deserialize to an empty Vector<Credential>:

-        credentials = (Vector<Credential>)ExtUtil.read(in,
-                new ExtWrapList(Credential.class), pf);
+        try {
+            credentials = (Vector<Credential>) ExtUtil.read(in,
+                    new ExtWrapList(Credential.class), pf);
+        } catch (java.io.EOFException eof) {
+            // Backward‐compat: profiles serialized before credentials existed
+            credentials = new Vector<>();
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
credentials = (Vector<Credential>)ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
}
try {
credentials = (Vector<Credential>) ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
} catch (java.io.EOFException eof) {
// Backward‐compat: profiles serialized before credentials existed
credentials = new Vector<>();
}
}
🤖 Prompt for AI Agents
In src/main/java/org/commcare/suite/model/Profile.java around lines 241 to 243,
the credentials deserialization should be guarded for backward compatibility:
wrap the ExtUtil.read(...) call in a try/catch that catches EOFException and,
when caught, assigns credentials = new Vector<Credential>() so legacy profiles
without credentials deserialize to an empty list; rethrow or let other
exceptions propagate unchanged so only missing-data EOF is handled.


@Override
Expand All @@ -244,5 +256,6 @@ public void writeExternal(DataOutputStream out) throws IOException {
ExtUtil.write(out, new ExtWrapMap(featureStatus));
ExtUtil.writeString(out, buildProfileId);
ExtUtil.write(out, new ExtWrapList(dependencies));
ExtUtil.write(out, new ExtWrapList(credentials));
}
}
28 changes: 28 additions & 0 deletions src/main/java/org/commcare/xml/ProfileParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
*/
package org.commcare.xml;

import org.commcare.cases.util.StringUtils;
import org.commcare.resources.model.Resource;
import org.commcare.resources.model.ResourceTable;
import org.commcare.suite.model.AndroidPackageDependency;
import org.commcare.suite.model.Credential;
import org.commcare.suite.model.Profile;
import org.commcare.util.CommCarePlatform;
import org.javarosa.core.reference.RootTranslator;
Expand All @@ -27,7 +29,11 @@ public class ProfileParser extends ElementParser<Profile> {

private static final String NAME_DEPENDENCIES = "dependencies";
private static final String NAME_ANDROID_PACKAGE = "android_package";
private static final String NAME_CREDENTIALS = "credentials";
private static final String NAME_CREDENTIAL = "credential";
private static final String ATTR_ID = "id";
private static final String ATTR_CREDENTIAL_LEVEL = "level";
private static final String ATTR_CREDENTIAL_TYPE = "type";

ResourceTable table;
String resourceId;
Expand Down Expand Up @@ -244,13 +250,35 @@ private void parseFeatures(Profile profile) throws XmlPullParserException, IOExc
} else if (tag.equals(NAME_DEPENDENCIES)) {
profile.setDependencies(parseDependencies());
} else if (tag.equals("sense")) {
}else if (tag.equals(NAME_CREDENTIALS)) {
profile.setCredentials(parseCredentials());
}

profile.setFeatureActive(tag, isActive);
//TODO: set feature activation in profile
}
}

private Vector<Credential> parseCredentials()
throws InvalidStructureException, XmlPullParserException, IOException {
Vector<Credential> appCredentials = new Vector<>();
while (nextTagInBlock(NAME_CREDENTIALS)) {
String tag = parser.getName().toLowerCase();
if (tag.equals(NAME_CREDENTIAL)) {
String level = parser.getAttributeValue(null, ATTR_CREDENTIAL_LEVEL);
String type = parser.getAttributeValue(null, ATTR_CREDENTIAL_TYPE);
if (StringUtils.isEmpty(level)) {
throw new InvalidStructureException("No level defined for credential");
}
if (StringUtils.isEmpty(type)) {
throw new InvalidStructureException("No type defined for credential");
}
appCredentials.add(new Credential(level, type));
}
}
return appCredentials;
}

private Vector<AndroidPackageDependency> parseDependencies()
throws InvalidStructureException, XmlPullParserException, IOException {
Vector<AndroidPackageDependency> appDependencies = new Vector<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.commcare.resources.model.Resource;
import org.commcare.resources.model.ResourceTable;
import org.commcare.suite.model.AndroidPackageDependency;
import org.commcare.suite.model.Credential;
import org.commcare.suite.model.Profile;
import org.commcare.test.utilities.PersistableSandbox;
import org.commcare.util.engine.CommCareConfigEngine;
Expand Down Expand Up @@ -92,6 +93,16 @@ public void testDependenciesParse() {
assertEquals(Arrays.toString(expectedDependencies),Arrays.toString(p.getDependencies().toArray()));
}

@Test
public void testCredentialsParse() {
Profile p = getProfile(BASIC_PROFILE_PATH);
assertTrue(p.isFeatureActive("credentials"));
Credential[] expectedCredentials = new Credential[2];
expectedCredentials[0] = new Credential("3MON_ACTIVE", "APP_ACTIVITY");
expectedCredentials[1] = new Credential("6MON_ACTIVE", "APP_ACTIVITY");
assertEquals(Arrays.toString(expectedCredentials),Arrays.toString(p.getCredentials().toArray()));
}

private void compareProfiles(Profile a, Profile b) {
if(!ArrayUtilities.arraysEqual(a.getPropertySetters(), b.getPropertySetters())) {
fail("Mismatch of property setters between profiles");
Expand Down
5 changes: 5 additions & 0 deletions src/test/resources/basic_profile.ccpr
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
<android_package id="org.commcare.test"/>
</dependencies>

<credentials active="true">
<credential level="3MON_ACTIVE" type="APP_ACTIVITY"></credential>
<credential level="6MON_ACTIVE" type="APP_ACTIVITY"></credential>
</credentials>

</features>

<suite><resource id="suite" version="102">
Expand Down
Loading