I have a result map like:
<resultMap type="cz.pse.agata.commons.dto.Security" id="detailed_security">
<result property="id" column="VAID"/>
<result property="issueId" column="VAIDVA"/>
<result property="effectiveDate" column="VAEFDT"/>
<result property="bic" column="VABIC"/>
.
.
.
<association property="isin" column="VACVAL" javaType="cz.pse.agata.commons.dto.ISIN">
<result property="isin" column="VACVAL"/>
</association>
<association property="legalPerson" javaType="cz.pse.agata.commons.dto.LegalPerson"
resultMap="legalPerson" columnPrefix="specialist_person_"/>
</association>
<association property="emitent" column="VAEMIT" javaType="cz.pse.agata.commons.dto.LegalPerson"
resultMap="cz.pse.agata.commons.dto.mapper.LegalPersonMapper.legalPerson"/>
<association property="administrator" column="VAIDAD" javaType="cz.pse.agata.commons.dto.LegalPerson"
resultMap="legalPerson"/>
<association property="changeReason" column="VAIDCD" javaType="cz.pse.agata.commons.dto.ChangeReason">
<result property="id" column="CDID"/>
<result property="reason" column="CDTEXT"/>
</association>
<association property="tail" javaType="Object">
<discriminator javaType="String" column="VATVAL">
<case value="000" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="520" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="100" resultMap="cz.pse.cp.dto.mapper.BondMapper.Bond"/>
<case value="150" resultMap="cz.pse.cp.dto.mapper.BondMapper.Bond"/>
<case value="200" resultMap="cz.pse.cp.dto.mapper.BondMapper.Bond"/>
<case value="500" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="510" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="610" resultMap="cz.pse.cp.dto.mapper.WarrantMapper.Warrant"/>
<case value="660" resultMap="cz.pse.cp.dto.mapper.WarrantMapper.Warrant">
<result property="id" column="WAID"/>
</case>
<case value="600" resultMap="cz.pse.cp.dto.mapper.InvestementCertificateMapper.InvestementCertificate"/>
<case value="650" resultMap="cz.pse.cp.dto.mapper.InvestementCertificateMapper.InvestementCertificate"/>
<case value="300" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="305" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
<case value="400" resultType="cz.pse.agata.commons.dto.SecurityTailImpl">
<result property="issueId" column="VAIDVA"/>
</case>
</discriminator>
</association>
</resultMap>
And I have a query, that returns 4 rows:
==> Preparing: with dates as ( select vaefdt as dat from ....
==> Parameters: 109735(Long), 109735(Long), ....
<== Total: 4
But the resulting list has 3 items only.
After some debugging I realized, that it happens (probably) because 2 of the 4 rows have all data the same except tail values:
(Method org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForNestedResultMap(ResultSetWrapper, ResultMap, ResultHandler, RowBounds, ResultMapping))
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rsw.getResultSet(), rowBounds);
Object rowValue = null;
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
Object partialObject = nestedResultObjects.get(rowKey);
// issue #577 && #542
if (mappedStatement.isResultOrdered()) {
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject);
} else {
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject);
if (partialObject == null) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
}
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
Note bold text above: for the 2nd iteration proper rowValue is created, but it is not added to the result, because partialObject is not null.
Any help would be appreciated.
Related
I'd like to restrict an API for a set of AD users using scopes and a set of daemon apps using app roles. However, following Azure APIM policy will check only if both claims are present. How do I rewrite the below policy to allow EITHER a scope OR an app role to be present in the JWT token:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
<claim name="roles" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
My idea is combine choose when and jwt-validate, here's my policy, it can choose to validate scp or roles, but I don't know why it can't correctly validate the value, I'm really not an expert in apim.
<inbound>
<base />
<set-variable name="isScp" value="#{
string isScp = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
if(jwt.Claims.GetValueOrDefault("scp", "null") != "null"){
isScp = "true";
}
}
}
}
return isScp;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault("isScp") == "false")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for roles">
<required-claims>
<claim name="roles" match="any">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</when>
<otherwise>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for scp">
<required-claims>
<claim name="scp" match="any" separator=" ">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</otherwise>
</choose>
</inbound>
By the way, the validation can also write in the code, and not to use validate-jwt, that will be :
<inbound>
<base />
<set-variable name="pass" value="#{
bool isContainScp = false;
bool isContainRoles = false;
string pass = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
string tempScp = jwt.Claims.GetValueOrDefault("scp", "null");
if(tempScp != "null"){
isContainScp = tempScp.Contains("User.ReadWrite.All");
}else{
//write logic here
isContainRoles = true;
}
}
}
}
if(isContainScp || isContainRoles){
pass = "true";
}
return pass;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault("pass") == "false")">
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</when>
<otherwise />
</choose>
</inbound>
Thanks Tiny Wang for giving me a the sample. authHeader already contains the token. Therefore, there is no need to split it further to get the token. Here is updated one:
<inbound>
<base />
<set-variable name="clientType" value="#{
bool isAuthorized = false;
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length == 0) {
return "No Authorization Header";
}
Jwt jwt;
if (!authHeader.TryParseJwt(out jwt)) {
return "Parse JWT Token failed";
}
bool isPublicClient = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("0");
bool isClientSecret = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("1");
bool isClientCertificate = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("2");
if (isPublicClient) {
return "Public Client";
} else if (isClientSecret) {
return "Client Secret";
} else if (isClientCertificate) {
return "Client Certificate";
}
return "Unauthorized";
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault("clientType") == "Public Client")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/YourTenantHere/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your audience here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/YourTenantHere/v2.0</issuer>
</issuers>
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<when condition="#(context.Variables.GetValueOrDefault("clientType") == "Client Secret")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/your tenant here/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your Audience Here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/Your tenant here/v2.0</issuer>
</issuers>
<required-claims>
<claim name="roles" match="any">
<value>App.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<otherwise>
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</otherwise>
</choose>
</inbound>
The high road to acheive the roles claim from JWT will be to add 'output-token-variable-name' prperty in validate-jwt policy.
Here is an example (the required claim here is 'emails' but the same idea):
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-scheme="Bearer" output-token-variable-name="jwt">
<openid-config url="https://csmsorg.b2clogin.com/csmsorg.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_susi_reset_v2" />
<audiences>
<audience>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx</audience>
</audiences>
<issuers>
<issuer>https://xxxxxx.b2clogin.com/e748014c-129c-4d13-accc-e5e1bb7c38eb/v2.0/</issuer>
</issuers>
<required-claims>
<claim name="tfp" match="any">
<value>B2C_1_susi_reset_v2</value>
<value>B2C_1__SUSI</value>
</claim>
</required-claims>
</validate-jwt>
<choose>
<when condition="#(!((Jwt)context.Variables["jwt"]).Claims["emails"].Contains("xxxxxx#gmail.com"))">
<return-response>
<set-status code="403" reason="Forbidden" />
</return-response>
</when>
</choose>
</inbound>
I'm working with react navigation 5 :
I created MainStackScreen and AuthStackScreen,
const AuthStack = createStackNavigator();
function AuthStackScreen() {
return (
<AuthStack.Navigator headerMode="none">
<AuthStack.Screen name="Main" component={Main} />
<AuthStack.Screen name="Login" component={Login} />
<AuthStack.Screen name="Registration" component={Registration} />
<AuthStack.Screen name="Home" component={DrawerNavigator} />
<AuthStack.Screen name="Notifications" component={Notifications} />
<AuthStack.Screen name="SmsValidation" component={SmsValidation} />
<AuthStack.Screen name="MoreRegistrationInfo" component={MoreRegistrationInfo} />
</AuthStack.Navigator>
);
}
MainStackScreen :
const MainScreen = createStackNavigator();
function MainStackScreen({navigation}) {
return (
<MainScreen.Navigator>
<MainScreen.Screen name="Main" component={Main} />
<MainScreen.Screen name="SmsValidation" component={SmsValidation} />
<MainScreen.Screen name="MoreRegistrationInfo" component={MoreRegistrationInfo} />
</MainScreen.Navigator>
);
}
I want to prevent IOS swipe action back between Login my screens
You can set gestureEnabled to false in a screen like:
<AuthStack.Screen
name="Login"
component={Login}
options={{gestureEnabled: false}}
/>
Or the whole navigator like:
<AuthStack.Navigator screenOptions={{gestureEnabled: false}}>
...
</AuthStack.Navigator>
/* -------------------------------------------------------------------------- */
/* AUTH STACK */
/* -------------------------------------------------------------------------- */
const AuthStack = createStackNavigator();
function AuthStackScreen() {
return (
<AuthStack.Navigator headerMode="none">
<AuthStack.Screen name="Main" component={Main} />
<AuthStack.Screen name="Login" component={Login} options={{gestureEnabled: false}} />
<AuthStack.Screen
name="Registration"
component={Registration}
options={{gestureEnabled: false}}
/>
<AuthStack.Screen name="Home" component={DrawerNavigator} options={{gestureEnabled: false}} />
<AuthStack.Screen
name="Notifications"
component={Notifications}
options={{gestureEnabled: false}}
/>
<AuthStack.Screen
name="SmsValidation"
component={SmsValidation}
options={{gestureEnabled: false}}
/>
<AuthStack.Screen
name="MoreRegistrationInfo"
component={MoreRegistrationInfo}
options={{gestureEnabled: false}}
/>
</AuthStack.Navigator>
);
}
You can set a beforeRemove listener to prevent going back.
React Navigation Docs
Using Visual Studio 2013, I created an Entity Model over an existing database. Each table has a GUID for it's primary key. I created an MVC Web API project with related OData Bindings and Controllers.
Here is how I create the OData Binding;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<HRPosition>("HRPositions").EntityType.HasKey(p=>p.HTPositionGuid);
Here is a sample controller for the HRPositions Entity.
public class HRPositionsController : ODataController
{
private EFSEntities db = new EFSEntities();
// GET: odata/HRPositions
[EnableQuery]
public IQueryable<HRPosition> GetHRPositions()
{
return db.HRPositions;
}
// GET: odata/HRPositions(5)
[EnableQuery]
public SingleResult<HRPosition> GetHRPosition([FromODataUri] Guid key)
{
return SingleResult.Create(db.HRPositions.Where(hRPosition => hRPosition.HTPositionGuid == key));
}
// PUT: odata/HRPositions(5)
public async Task<IHttpActionResult> Put([FromODataUri] Guid key, Delta<HRPosition> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
HRPosition hRPosition = await db.HRPositions.FindAsync(key);
if (hRPosition == null)
{
return NotFound();
}
patch.Put(hRPosition);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!HRPositionExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(hRPosition);
}
// POST: odata/HRPositions
public async Task<IHttpActionResult> Post(HRPosition hRPosition)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.HRPositions.Add(hRPosition);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (HRPositionExists(hRPosition.HTPositionGuid))
{
return Conflict();
}
else
{
throw;
}
}
return Created(hRPosition);
}
// PATCH: odata/HRPositions(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] Guid key, Delta<HRPosition> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
HRPosition hRPosition = await db.HRPositions.FindAsync(key);
if (hRPosition == null)
{
return NotFound();
}
patch.Patch(hRPosition);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!HRPositionExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(hRPosition);
}
// DELETE: odata/HRPositions(5)
public async Task<IHttpActionResult> Delete([FromODataUri] Guid key)
{
HRPosition hRPosition = await db.HRPositions.FindAsync(key);
if (hRPosition == null)
{
return NotFound();
}
db.HRPositions.Remove(hRPosition);
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool HRPositionExists(Guid key)
{
return db.HRPositions.Count(e => e.HTPositionGuid == key) > 0;
}
}
Once the OData Service is deployed and using Fiddler I am able to query the Service endpoints and retrieve the full list of data as well as single entity data.
I then created a SharePoint App in which I create an External Content Type by referencing the OData service. This creates the ECT Model definitions for each endpoint.
Here is the ECT for HRPositions;
<?xml version="1.0" encoding="utf-16"?>
<Model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="EFSData" xmlns="http://schemas.microsoft.com/windows/2007/BusinessDataCatalog">
<LobSystems>
<LobSystem Name="EFSODATA" Type="OData">
<Properties>
<Property Name="ODataServiceMetadataUrl" Type="System.String">https://efsodataapi.azurewebsites.net/OData/$metadata</Property>
<Property Name="ODataServiceMetadataAuthenticationMode" Type="System.String">PassThrough</Property>
<Property Name="ODataServicesVersion" Type="System.String">2.0</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<LobSystemInstances>
<LobSystemInstance Name="EFSODATA">
<Properties>
<Property Name="ODataServiceUrl" Type="System.String">https://efsodataapi.azurewebsites.net/OData</Property>
<Property Name="ODataServiceAuthenticationMode" Type="System.String">PassThrough</Property>
<Property Name="ODataFormat" Type="System.String">application/atom+xml</Property>
<Property Name="HttpHeaderSetAcceptLanguage" Type="System.Boolean">true</Property>
</Properties>
</LobSystemInstance>
</LobSystemInstances>
<Entities>
<Entity Name="HRPositions" DefaultDisplayName="HRPositions" Namespace="EFSData" Version="1.0.0.0" EstimatedInstanceCount="2000">
<Properties>
<Property Name="ExcludeFromOfflineClientForList" Type="System.String">False</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<Identifiers>
<Identifier Name="HTPositionGuid" TypeName="System.Guid" />
</Identifiers>
<Methods>
<Method Name="CreateHRPosition" DefaultDisplayName="Create HRPosition" IsStatic="false">
<Properties>
<Property Name="ODataEntityUrl" Type="System.String">/HRPositions</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<Parameters>
<Parameter Name="#HTPositionGuid" Direction="In">
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" CreatorField="true" />
</Parameter>
<Parameter Name="#PosistionCode" Direction="In">
<TypeDescriptor Name="PosistionCode" DefaultDisplayName="PosistionCode" TypeName="System.String" CreatorField="true" />
</Parameter>
<Parameter Name="#PositionName" Direction="In">
<TypeDescriptor Name="PositionName" DefaultDisplayName="PositionName" TypeName="System.String" CreatorField="true" />
</Parameter>
<Parameter Name="#Description" Direction="In">
<TypeDescriptor Name="Description" DefaultDisplayName="Description" TypeName="System.String" CreatorField="true" />
</Parameter>
<Parameter Name="#CreateHRPosition" Direction="Return">
<TypeDescriptor Name="CreateHRPosition" DefaultDisplayName="CreateHRPosition" TypeName="Microsoft.BusinessData.Runtime.DynamicType">
<TypeDescriptors>
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" ReadOnly="true" />
<TypeDescriptor Name="PosistionCode" DefaultDisplayName="PosistionCode" TypeName="System.String" />
<TypeDescriptor Name="PositionName" DefaultDisplayName="PositionName" TypeName="System.String" />
<TypeDescriptor Name="Description" DefaultDisplayName="Description" TypeName="System.String" />
</TypeDescriptors>
</TypeDescriptor>
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstance Name="CreateHRPosition" Type="Creator" ReturnParameterName="#CreateHRPosition" ReturnTypeDescriptorPath="CreateHRPosition">
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
</MethodInstance>
</MethodInstances>
</Method>
<Method Name="ReadSpecificHRPosition" DefaultDisplayName="Read Specific HRPosition" IsStatic="false">
<Properties>
<Property Name="ODataEntityUrl" Type="System.String">/HRPositions(HTPositionGuid=guid'#HTPositionGuid')</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<Parameters>
<Parameter Name="#HTPositionGuid" Direction="In">
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" />
</Parameter>
<Parameter Name="#HRPosition" Direction="Return">
<TypeDescriptor Name="HRPosition" DefaultDisplayName="HRPosition" TypeName="Microsoft.BusinessData.Runtime.DynamicType">
<TypeDescriptors>
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" ReadOnly="true" />
<TypeDescriptor Name="PosistionCode" DefaultDisplayName="PosistionCode" TypeName="System.String" />
<TypeDescriptor Name="PositionName" DefaultDisplayName="PositionName" TypeName="System.String" />
<TypeDescriptor Name="Description" DefaultDisplayName="Description" TypeName="System.String" />
</TypeDescriptors>
</TypeDescriptor>
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstance Name="ReadSpecificHRPosition" Type="SpecificFinder" Default="true" ReturnParameterName="#HRPosition" ReturnTypeDescriptorPath="HRPosition">
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
</MethodInstance>
</MethodInstances>
</Method>
<Method Name="ReadAllHRPosition" DefaultDisplayName="Read All HRPosition" IsStatic="false">
<Properties>
<Property Name="ODataEntityUrl" Type="System.String">/HRPositions?$top=#LimitHRPositionss</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<FilterDescriptors>
<FilterDescriptor Name="LimitFilter" DefaultDisplayName="LimitFilter" Type="Limit" />
</FilterDescriptors>
<Parameters>
<Parameter Name="#LimitHRPositionss" Direction="In">
<TypeDescriptor Name="LimitHRPositionss" DefaultDisplayName="LimitHRPositionss" TypeName="System.Int32" AssociatedFilter="LimitFilter">
<Properties>
<Property Name="LogicalOperatorWithPrevious" Type="System.String">None</Property>
<Property Name="Order" Type="System.String">0</Property>
</Properties>
<DefaultValues>
<DefaultValue MethodInstanceName="ReadAllHRPosition" Type="System.Int32">100</DefaultValue>
</DefaultValues>
</TypeDescriptor>
</Parameter>
<Parameter Name="#HRPositions" Direction="Return">
<TypeDescriptor Name="HRPositions" DefaultDisplayName="HRPositions" TypeName="Microsoft.BusinessData.Runtime.IDynamicTypeEnumerator" IsCollection="true">
<TypeDescriptors>
<TypeDescriptor Name="HRPosition" DefaultDisplayName="HRPosition" TypeName="Microsoft.BusinessData.Runtime.DynamicType">
<TypeDescriptors>
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" ReadOnly="true" />
<TypeDescriptor Name="PosistionCode" DefaultDisplayName="PosistionCode" TypeName="System.String" />
<TypeDescriptor Name="PositionName" DefaultDisplayName="PositionName" TypeName="System.String" />
<TypeDescriptor Name="Description" DefaultDisplayName="Description" TypeName="System.String" />
</TypeDescriptors>
</TypeDescriptor>
</TypeDescriptors>
</TypeDescriptor>
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstance Name="ReadAllHRPosition" Type="Finder" Default="true" ReturnParameterName="#HRPositions" ReturnTypeDescriptorPath="HRPositions">
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
</MethodInstance>
</MethodInstances>
</Method>
<Method Name="UpdateHRPosition" DefaultDisplayName="Update HRPosition" IsStatic="false">
<Properties>
<Property Name="ODataEntityUrl" Type="System.String">/HRPositions(HTPositionGuid=guid'#HTPositionGuid')</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<Parameters>
<Parameter Name="#HTPositionGuid" Direction="In">
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" UpdaterField="true" />
</Parameter>
<Parameter Name="#PosistionCode" Direction="In">
<TypeDescriptor Name="PosistionCode" DefaultDisplayName="PosistionCode" TypeName="System.String" UpdaterField="true" />
</Parameter>
<Parameter Name="#PositionName" Direction="In">
<TypeDescriptor Name="PositionName" DefaultDisplayName="PositionName" TypeName="System.String" UpdaterField="true" />
</Parameter>
<Parameter Name="#Description" Direction="In">
<TypeDescriptor Name="Description" DefaultDisplayName="Description" TypeName="System.String" UpdaterField="true" />
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstance Name="UpdateHRPosition" Type="Updater">
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
</MethodInstance>
</MethodInstances>
</Method>
<Method Name="DeleteHRPosition" DefaultDisplayName="Delete HRPosition" IsStatic="false">
<Properties>
<Property Name="ODataEntityUrl" Type="System.String">/HRPositions(HTPositionGuid=guid'#HTPositionGuid')</Property>
</Properties>
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
<Parameters>
<Parameter Name="#HTPositionGuid" Direction="In">
<TypeDescriptor Name="HTPositionGuid" DefaultDisplayName="HTPositionGuid" TypeName="System.Guid" IdentifierName="HTPositionGuid" />
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstance Name="DeleteHRPosition" Type="Deleter">
<AccessControlList>
<AccessControlEntry Principal="STS|SecurityTokenService|http://sharepoint.microsoft.com/claims/2009/08/isauthenticated|true|http://www.w3.org/2001/XMLSchema#string">
<Right BdcRight="Edit" />
<Right BdcRight="Execute" />
<Right BdcRight="SelectableInClients" />
<Right BdcRight="SetPermissions" />
</AccessControlEntry>
</AccessControlList>
</MethodInstance>
</MethodInstances>
</Method>
</Methods>
</Entity>
</Entities>
</LobSystem>
</LobSystems>
</Model>
I uploaded the ECT into SharePoint Online BCS and all looks fine;
From there I create an external list and reference the HRPositions ECT, which creates and SP List but is missing the primary key (which is the GUID).
This view shows the proper data;
I am able to add a new item to the list;
And it shows in the read all view;
But I can't edit, delete or view any list item as I get this error for each operation;
I attached to the OData Web Service and could see why the issue is occurring. Turns out the Auto-Generated External Control Types (ECT) within Visual Studio that were reflected off the OData Service have an issue in that for some reason it is formulating the request as /HRPositions(HTPositionGuid=guid'#HTPositionGuid');
It should really only be /HRPositions(guid'#HTPositionGuid');
Can anyone tell me why it's including the HTPositionGuid= within the Parameter list?
I can manually edit the code-generated ECT files for each entity but that seems silly.
I am not sure why this was happening. I have a struts form that will post to my struts action's method:
<s:form data-dojo-type="dijit/form/Form" action="SaveRec" method="POST"
enctype="multipart/form-data" theme="simple" id="frmRecord">
<div class="item">
...
Basically, I just hit the link htp://example.com:8080/Test/SaveRec in browser and I will get a workable page, but after using these code, I will get the post 404 error(POST http://example.com:8080/Test/SaveRec.action 404 (Not Found) ):
dojo.connect(dojo.byId('btnSave'), 'onclick', function(event){
// The parameters to pass to xhrPost, the message, and the url to send it to
// Also, how to handle the return and callbacks.
var xhrArgs = {
form: dojo.byId("frmRecord"),
handleAs: "json",
load: function(data){
console.log("Message posted. " + data);
},
error: function(error){
console.log("Message failed to post, " + error);
}
}
console.log("Message being sent...");
// Call the asynchronous xhrPost
var deferred = dojo.xhrPost(xhrArgs);
});
PS: I can run invocation.invoke() and print out message in my interceptor
UPDATE:
OK, I think the problem is the return type of action, I have added the interceptor into the action, however, I still cannot use the getter to retrieve the value:
<struts>
<package name="testJson" extends="json-default">
<action name="SaveRec" class="com.xyz.Test" method="saveRec">
<interceptor-ref name="json">
<param name="contentType">application/json</param>
</interceptor-ref>
<result name="success" type="json">
<param name="root">saveResult</param>
</result>
</action>
...
I'm trying to transition this bit of code from scrubyt to nokogiri, and am stuck trying to write my results to either a hash or xml. In scrubyt it looks like the following:
require 'rubygems'
require 'scrubyt'
result_data = Scrubyt::Extractor.define do
fetch "http://www.amazon.com/gp/offer-listing/0061673730"
results "//div[#class='resultsset']" do
item "//tbody/tr" do
condition "//div[#class = 'Condition']"
price "//span[#class = 'price']"
shipping "//span[#class = 'price_shipping']"
end
end
end
#description = result_data.to_xml
return #description
end
With nokogiri I can parse out the information I want, but there doesn't seem to be a quick way to return items in a hash or xml document. Here's all I have in nokogiri.
require 'rubygems'
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML(open('http://www.amazon.com/gp/offer-listing/0061673730'))
doc.css('div.condition, span.price, span.price_shipping ').each do |item|
puts item.content
end
How would one return item information to either xml or a hash?
You can use the Builder to build XML.
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.items {
doc.css('div.condition, span.price, span.price_shipping').each do |o|
xml.item_content = o
end
}
}
end
puts builder.to_xml
Figured it out...
require 'rubygems'
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML(open('http://www.amazon.com/gp/offer-listing/0061673730'))
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
doc.xpath('//tbody[#class="result"]').each do |res|
xml.result {
res.css('span.price').each do |p|
xml.price = p.content
end
res.css('span.price_shipping').each do |s|
xml.ship = s.content
end
}
end
}
end
puts builder.to_xml
Results:
<?xml version="1.0"?>
<root>
<result>
<price=>$6.09</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$6.48</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.12</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.31</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.52</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.52</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$11.53</price=>
</result>
<result>
<price=>$7.56</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.61</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.61</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.95</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$7.95</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$8.59</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$8.99</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$10.05</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$10.32</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$10.32</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$10.55</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$10.56</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$11.42</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$11.59</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$11.90</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$11.95</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$12.07</price=>
<ship=>+ $3.99</ship=>
</result>
<result>
<price=>$12.35</price=>
<ship=>+ $3.99</ship=>
</result>
</root>
Thanks! That's exactly what I need. I'm having trouble looping correctly, though.
require 'rubygems'
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML(open('http://www.amazon.com/gp/offer-listing/0061673730'))
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.item {
doc.css('span.price').each do |o|
xml.price = o
doc.css('span.price_shipping').each do |o|
end
end
}
}
end
puts builder.to_xml
That returns this:
<?xml version="1.0"?>
<root>
<item>
<price=><span class="price">$6.09</span></price=>
<price=><span class="price">$6.48</span></price=>
<price=><span class="price">$11.95</span></price=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
</item>
</root>
How would I rewrite my code to return something like this:
<?xml version="1.0"?>
<root>
<item>
<price=><span class="price">$6.09</span></price=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
</item>
<item>
<price=><span class="price">$6.48</span></price=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
</item>
<item>
<price=><span class="price">$11.95</span></price=>
<ship=><span class="price_shipping">+ $3.99</span></ship=>
</item>
</root>
you may want to omit "=" in
xml.price = p.content